123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 |
- /*
- ===========================================================================
- 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_maxRate( "net_maxRate", "50", CVAR_INTEGER, "max send rate in kilobytes per second" );
- idCVar net_showReliableCompression( "net_showReliableCompression", "0", CVAR_BOOL, "Show reliable compression ratio." );
- // we use an assert(0); return idiom in some places, which lint complains about
- //lint -e527 unreachable code at token 'return'
- /*
- ================================================
- idPacketProcessor::QueueReliableAck
- ================================================
- */
- void idPacketProcessor::QueueReliableAck( int lastReliable ) {
- // NOTE - Even if it was the last known sequence, go ahead and ack it, in case our last ack for this sequence got dropped
- if ( lastReliable >= reliableSequenceRecv ) {
- queuedReliableAck = lastReliable;
- reliableSequenceRecv = lastReliable;
- }
- }
- /*
- ================================================
- idPacketProcessor::FinalizeRead
- ================================================
- */
- int idPacketProcessor::FinalizeRead( idBitMsg & inMsg, idBitMsg & outMsg, int & userValue ) {
- userValue = 0;
-
- idInnerPacketHeader header;
- header.ReadFromMsg( inMsg );
-
- if ( !verify( header.Type() != PACKET_TYPE_FRAGMENTED ) ) { // We shouldn't be fragmented at this point
- idLib::Printf("Received invalid fragmented packet.\n" );
- return RETURN_TYPE_NONE;
- }
- if ( header.Type() == PACKET_TYPE_RELIABLE_ACK ) {
- // Handle reliable ack
- int reliableSequence = inMsg.ReadLong();
- reliable.RemoveOlderThan( reliableSequence + 1 );
- header.ReadFromMsg( inMsg ); // Read the new header, since the reliable ack sits on top the actual header of the message
- }
- if ( header.Type() == PACKET_TYPE_OOB ) {
- // out-of-band packet
- userValue = header.Value();
- } else {
- // At this point, this MUST be an in-band packet
- if ( !verify( header.Type() == PACKET_TYPE_INBAND ) ) {
- idLib::Printf("In-band packet expected, received type %i instead.\n", header.Type() );
- return RETURN_TYPE_NONE;
- }
-
- // Reset number of reliables received (NOTE - This means you MUST unload all reliables as they are received)
- numReliable = 0;
-
- // Handle reliable portion of in-band packets
- int numReliableRecv = header.Value();
- int bufferPos = 0;
-
- if ( numReliableRecv > 0 ) {
- // Byte align msg
- inMsg.ReadByteAlign();
- int compressedSize = inMsg.ReadShort();
- lzwCompressionData_t lzwData;
- idLZWCompressor lzwCompressor( &lzwData );
-
- lzwCompressor.Start( (uint8*)inMsg.GetReadData() + inMsg.GetReadCount(), compressedSize ); // Read from msg
- int reliableSequence = 0;
- lzwCompressor.ReadAgnostic< int >( reliableSequence );
- for ( int r = 0; r < numReliableRecv; r++ ) {
- uint8 uncompMem[ MAX_MSG_SIZE ];
- uint16 reliableDataLength = 0;
- lzwCompressor.ReadAgnostic< uint16 >( reliableDataLength );
- lzwCompressor.Read( uncompMem, reliableDataLength );
- if ( reliableSequence + r > reliableSequenceRecv ) { // Only accept newer reliable msg's than we've currently already received
- if ( !verify( bufferPos + reliableDataLength <= sizeof( reliableBuffer ) ) ) {
- idLib::Printf( "Reliable msg size overflow.\n" );
- return RETURN_TYPE_NONE;
- }
- if ( !verify( numReliable < MAX_RELIABLE_QUEUE ) ) {
- idLib::Printf( "Reliable msg count overflow.\n" );
- return RETURN_TYPE_NONE;
- }
- memcpy( reliableBuffer + bufferPos, uncompMem, reliableDataLength );
- reliableMsgSize[ numReliable ] = reliableDataLength;
- reliableMsgPtrs[ numReliable++ ] = &reliableBuffer[ bufferPos ];
- bufferPos += reliableDataLength;
- } else {
- extern idCVar net_verboseReliable;
- if ( net_verboseReliable.GetBool() ) {
- idLib::Printf( "Ignoring reliable msg %i because %i was already acked\n", ( reliableSequence + r ), reliableSequenceRecv );
- }
- }
-
- if ( !verify( lzwCompressor.IsOverflowed() == false ) ) {
- idLib::Printf( "lzwCompressor.IsOverflowed() == true.\n" );
- return RETURN_TYPE_NONE;
- }
- }
-
- inMsg.SetReadCount( inMsg.GetReadCount() + compressedSize );
- QueueReliableAck( reliableSequence + numReliableRecv - 1 );
- }
- }
-
- // Load actual msg
- outMsg.BeginWriting();
- outMsg.WriteData( inMsg.GetReadData() + inMsg.GetReadCount(), inMsg.GetRemainingData() );
- outMsg.SetSize( inMsg.GetRemainingData() );
-
- return ( header.Type() == PACKET_TYPE_OOB ) ? RETURN_TYPE_OOB : RETURN_TYPE_INBAND;
- }
- /*
- ================================================
- idPacketProcessor::QueueReliableMessage
- ================================================
- */
- bool idPacketProcessor::QueueReliableMessage( byte type, const byte * data, int dataLen ) {
- return reliable.Append( reliableSequenceSend++, &type, 1, data, dataLen );
- }
- /*
- ========================
- idPacketProcessor::CanSendMoreData
- ========================
- */
- bool idPacketProcessor::CanSendMoreData() const {
- if ( net_maxRate.GetInteger() == 0 ) {
- return true;
- }
- return ( outgoingRateBytes <= net_maxRate.GetInteger() * 1024 );
- }
- /*
- ========================
- idPacketProcessor::UpdateOutgoingRate
- ========================
- */
- void idPacketProcessor::UpdateOutgoingRate( const int time, const int size ) {
- outgoingBytes += size;
- // update outgoing rate variables
- if ( time > outgoingRateTime ) {
- outgoingRateBytes -= outgoingRateBytes * (float)( time - outgoingRateTime ) / 1000.0f;
- if ( outgoingRateBytes < 0.0f ) {
- outgoingRateBytes = 0.0f;
- }
- }
-
- outgoingRateTime = time;
- outgoingRateBytes += size;
- // compute an average bandwidth at intervals
- if ( time - lastOutgoingRateTime > BANDWIDTH_AVERAGE_PERIOD ) {
- currentOutgoingRate = 1000 * ( outgoingBytes - lastOutgoingBytes ) / ( time - lastOutgoingRateTime );
- lastOutgoingBytes = outgoingBytes;
- lastOutgoingRateTime = time;
- }
- }
- /*
- =================
- idPacketProcessor::UpdateIncomingRate
- =================
- */
- void idPacketProcessor::UpdateIncomingRate( const int time, const int size ) {
- incomingBytes += size;
- // update incoming rate variables
- if ( time > incomingRateTime ) {
- incomingRateBytes -= incomingRateBytes * (float)( time - incomingRateTime ) / 1000.0f;
- if ( incomingRateBytes < 0.0f ) {
- incomingRateBytes = 0.0f;
- }
- }
- incomingRateTime = time;
- incomingRateBytes += size;
- // compute an average bandwidth at intervals
- if ( time - lastIncomingRateTime > BANDWIDTH_AVERAGE_PERIOD ) {
- currentIncomingRate = 1000 * ( incomingBytes - lastIncomingBytes ) / ( time - lastIncomingRateTime );
- lastIncomingBytes = incomingBytes;
- lastIncomingRateTime = time;
- }
- }
- /*
- ================================================
- idPacketProcessor::ProcessOutgoing
- NOTE - We only compress reliables because we assume everything else has already been compressed.
- ================================================
- */
- bool idPacketProcessor::ProcessOutgoing( const int time, const idBitMsg & msg, bool isOOB, int userData ) {
- // We can only do ONE ProcessOutgoing call, then we need to do GetSendFragment to
- // COMPLETELY empty unsentMsg before calling ProcessOutgoing again.
- if ( !verify( fragmentedSend == false ) ) {
- idLib::Warning( "ProcessOutgoing: fragmentedSend == true!");
- return false;
- }
-
- if ( !verify( unsentMsg.GetRemainingData() == 0 ) ) {
- idLib::Warning( "ProcessOutgoing: unsentMsg.GetRemainingData() > 0!");
- return false;
- }
-
- // Build the full msg to send, which could include reliable data
- unsentMsg.InitWrite( unsentBuffer, sizeof( unsentBuffer ) );
- unsentMsg.BeginWriting();
-
- // Ack reliables if we need to (NOTE - We will send this ack on both the in-band and out-of-band channels)
- if ( queuedReliableAck >= 0 ) {
- idInnerPacketHeader header( PACKET_TYPE_RELIABLE_ACK, 0 );
- header.WriteToMsg( unsentMsg );
- unsentMsg.WriteLong( queuedReliableAck );
- queuedReliableAck = -1;
- }
- if ( isOOB ) {
- if ( msg.GetSize() + unsentMsg.GetSize() > MAX_OOB_MSG_SIZE ) { // Fragmentation not allowed for out-of-band msg's
- idLib::Printf("Out-of-band packet too large %i\n", unsentMsg.GetSize() );
- assert( 0 );
- return false;
- }
- // We don't need to worry about reliable for out of band packets
- idInnerPacketHeader header( PACKET_TYPE_OOB, userData );
- header.WriteToMsg( unsentMsg );
- } else {
- // Add reliable msg's here if this is an in-band packet
- idInnerPacketHeader header( PACKET_TYPE_INBAND, reliable.Num() );
- header.WriteToMsg( unsentMsg );
- if ( reliable.Num() > 0 ) {
- // Byte align unsentMsg
- unsentMsg.WriteByteAlign();
-
- lzwCompressionData_t lzwData;
- idLZWCompressor lzwCompressor( &lzwData );
- lzwCompressor.Start( unsentMsg.GetWriteData() + unsentMsg.GetSize() + 2, unsentMsg.GetRemainingSpace() - 2 ); // Write to compressed mem, not exceeding MAX_MSG_SIZE (+2 to reserve space for compressed size)
- int uncompressedSize = 4;
- lzwCompressor.WriteAgnostic< int >( reliable.ItemSequence( 0 ) );
- for ( int i = 0; i < reliable.Num(); i++ ) {
- lzwCompressor.WriteAgnostic< uint16 >( reliable.ItemLength( i ) );
- lzwCompressor.Write( reliable.ItemData( i ), reliable.ItemLength( i ) );
- uncompressedSize += 2 + reliable.ItemLength( i );
- }
- lzwCompressor.End();
- if ( lzwCompressor.IsOverflowed() ) {
- idLib::Error( "reliable msg compressor overflow." );
- }
- unsentMsg.WriteShort( lzwCompressor.Length() );
- unsentMsg.SetSize( unsentMsg.GetSize() + lzwCompressor.Length() );
- if ( net_showReliableCompression.GetBool() ) {
- static int totalUncompressed = 0;
- static int totalCompressed = 0;
- totalUncompressed += uncompressedSize;
- totalCompressed += lzwCompressor.Length();
- float ratio1 = (float)lzwCompressor.Length() / (float)uncompressedSize;
- float ratio2 = (float)totalCompressed / (float)totalUncompressed;
- idLib::Printf( "Uncompressed: %i, Compressed: %i, TotalUncompressed: %i, TotalCompressed: %i, (%2.2f / %2.2f )\n", uncompressedSize, lzwCompressor.Length(), totalUncompressed, totalCompressed, ratio1, ratio2 );
- }
- }
- }
-
- // Fill up with actual msg
- unsentMsg.WriteData( msg.GetReadData(), msg.GetSize() );
- if ( unsentMsg.GetSize() > MAX_PACKET_SIZE ) {
- if ( isOOB ) {
- idLib::Error( "oob msg's cannot fragment" );
- }
- fragmentedSend = true;
- }
-
- return true;
- }
- /*
- ================================================
- idPacketProcessor::GetSendFragment
- ================================================
- */
- bool idPacketProcessor::GetSendFragment( const int time, sessionId_t sessionID, idBitMsg & outMsg ) {
- lastSendTime = time;
- if ( unsentMsg.GetRemainingData() <= 0 ) {
- return false; // Nothing to send
- }
-
- outMsg.BeginWriting();
- idOuterPacketHeader outerHeader( sessionID );
- // Write outer packet header to the msg
- outerHeader.WriteToMsg( outMsg );
-
- if ( !fragmentedSend ) {
- // Simple case, no fragments to sent
- outMsg.WriteData( unsentMsg.GetReadData(), unsentMsg.GetSize() );
- unsentMsg.SetSize( 0 );
- } else {
- int currentSize = idMath::ClampInt( 0, MAX_PACKET_SIZE, unsentMsg.GetRemainingData() );
- assert( currentSize > 0 );
- assert( unsentMsg.GetRemainingData() - currentSize >= 0 );
-
- // See if we'll have more fragments once we subtract off how much we're about to write
- bool moreFragments = ( unsentMsg.GetRemainingData() - currentSize > 0 ) ? true : false;
-
- if ( !unsentMsg.GetReadCount() ) { // If this is the first read, then we know it's the first fragment
- assert( moreFragments ); // If we have a first, we must have more or something went wrong
- idInnerPacketHeader header( PACKET_TYPE_FRAGMENTED, FRAGMENT_START );
- header.WriteToMsg( outMsg );
- } else {
- idInnerPacketHeader header( PACKET_TYPE_FRAGMENTED, moreFragments ? FRAGMENT_MIDDLE : FRAGMENT_END );
- header.WriteToMsg( outMsg );
- }
-
- outMsg.WriteLong( fragmentSequence );
- outMsg.WriteData( unsentMsg.GetReadData() + unsentMsg.GetReadCount(), currentSize );
- unsentMsg.ReadData( NULL, currentSize );
- assert( moreFragments == unsentMsg.GetRemainingData() > 0 );
- fragmentedSend = moreFragments;
-
- fragmentSequence++; // Advance sequence
- fragmentAccumulator++; // update the counter for the net debug hud
- }
- // The caller needs to send this packet, so assume he did, and update rates
- UpdateOutgoingRate( time, outMsg.GetSize() );
-
- return true;
- }
- /*
- ================================================
- idPacketProcessor::ProcessIncoming
- ================================================
- */
- int idPacketProcessor::ProcessIncoming( int time, sessionId_t expectedSessionID, idBitMsg & msg, idBitMsg & out, int & userData, const int peerNum ) {
- assert( msg.GetSize() <= MAX_FINAL_PACKET_SIZE );
-
- UpdateIncomingRate( time, msg.GetSize() );
- idOuterPacketHeader outerHeader;
- outerHeader.ReadFromMsg( msg );
- sessionId_t sessionID = outerHeader.GetSessionID();
- assert( sessionID == expectedSessionID );
-
- if ( !verify( sessionID != SESSION_ID_CONNECTIONLESS_PARTY && sessionID != SESSION_ID_CONNECTIONLESS_GAME && sessionID != SESSION_ID_CONNECTIONLESS_GAME_STATE ) ) {
- idLib::Printf( "Expected non connectionless ID, but got a connectionless one\n" );
- return RETURN_TYPE_NONE;
- }
-
- if ( sessionID != expectedSessionID ) {
- idLib::Printf( "Expected session id: %8x but got %8x instead\n", expectedSessionID, sessionID );
- return RETURN_TYPE_NONE;
- }
- int c,b;
- msg.SaveReadState( c, b );
- idInnerPacketHeader header;
- header.ReadFromMsg( msg );
- if ( header.Type() != PACKET_TYPE_FRAGMENTED ) {
- // Non fragmented
- msg.RestoreReadState( c, b ); // Reset since we took a byte to check the type
- return FinalizeRead( msg, out, userData );
- }
- // Decode fragmented packet
- int readSequence = msg.ReadLong(); // Read sequence of fragment
- if ( header.Value() == FRAGMENT_START ) {
- msgWritePos = 0; // Reset msg reconstruction write pos
- } else if ( fragmentSequence == -1 || readSequence != fragmentSequence + 1 ) {
- droppedFrags++;
- idLib::Printf( "Dropped Fragments - PeerNum: %i FragmentSeq: %i, ReadSeq: %i, Total: %i\n", peerNum, fragmentSequence, readSequence, droppedFrags );
- // If this is the middle or end, make sure we are reading in fragmentSequence
- fragmentSequence = -1;
- return RETURN_TYPE_NONE; // Out of sequence
- }
- fragmentSequence = readSequence;
- assert( msg.GetRemainingData() > 0 );
- if ( !verify( msgWritePos + msg.GetRemainingData() < sizeof( msgBuffer ) ) ) {
- idLib::Error( "ProcessIncoming: Fragmented msg buffer overflow." );
- }
- memcpy( msgBuffer + msgWritePos, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() );
- msgWritePos += msg.GetRemainingData();
-
- if ( header.Value() == FRAGMENT_END ) {
- // Done reconstructing the msg
- idBitMsg msg( msgBuffer, sizeof( msgBuffer ) );
- msg.SetSize( msgWritePos );
- return FinalizeRead( msg, out, userData );
- }
-
- if ( !verify( header.Value() == FRAGMENT_START || header.Value() == FRAGMENT_MIDDLE ) ) {
- idLib::Printf( "ProcessIncoming: Invalid packet.\n" );
- }
- // If we get here, this is part (either beginning or end) of a fragmented packet.
- // We return RETURN_TYPE_NONE to let the caller know they don't need to do anything yet.
- return RETURN_TYPE_NONE;
- }
-
- /*
- ================================================
- idPacketProcessor::ProcessConnectionlessOutgoing
- ================================================
- */
- bool idPacketProcessor::ProcessConnectionlessOutgoing( idBitMsg & msg, idBitMsg & out, int lobbyType, int userData ) {
- sessionId_t sessionID = lobbyType + 1;
-
- // Write outer header
- idOuterPacketHeader outerHeader( sessionID );
- outerHeader.WriteToMsg( out );
- // Write inner header
- idInnerPacketHeader header( PACKET_TYPE_OOB, userData );
- header.WriteToMsg( out );
- // Write msg
- out.WriteData( msg.GetReadData(), msg.GetSize() );
- return true;
- }
- /*
- ================================================
- idPacketProcessor::ProcessConnectionlessIncoming
- ================================================
- */
- bool idPacketProcessor::ProcessConnectionlessIncoming( idBitMsg & msg, idBitMsg & out, int & userData ) {
- idOuterPacketHeader outerHeader;
- outerHeader.ReadFromMsg( msg );
- sessionId_t sessionID = outerHeader.GetSessionID();
- if ( sessionID != SESSION_ID_CONNECTIONLESS_PARTY && sessionID != SESSION_ID_CONNECTIONLESS_GAME && sessionID != SESSION_ID_CONNECTIONLESS_GAME_STATE ) {
- // Not a connectionless msg (this can happen if a previously connected peer keeps sending data for whatever reason)
- idLib::Printf( "ProcessConnectionlessIncoming: Invalid session ID - %d\n", sessionID );
- return false;
- }
- idInnerPacketHeader header;
- header.ReadFromMsg( msg );
- if ( header.Type() != PACKET_TYPE_OOB ) {
- idLib::Printf( "ProcessConnectionlessIncoming: header.Type() != PACKET_TYPE_OOB\n" );
- return false; // Only out-of-band packets supported for connectionless
- }
-
- userData = header.Value();
-
- out.BeginWriting();
- out.WriteData( msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() );
- out.SetSize( msg.GetRemainingData() );
- return true;
- }
- /*
- ================================================
- idPacketProcessor::GetSessionID
- ================================================
- */
- idPacketProcessor::sessionId_t idPacketProcessor::GetSessionID( idBitMsg & msg ) {
- sessionId_t sessionID;
- int c,b;
- msg.SaveReadState( c, b );
- // Read outer header
- idOuterPacketHeader outerHeader;
- outerHeader.ReadFromMsg( msg );
-
- // Get session ID
- sessionID = outerHeader.GetSessionID();
- msg.RestoreReadState( c, b );
- return sessionID;
- }
- /*
- ================================================
- idPacketProcessor::VerifyEmptyReliableQueue
- ================================================
- */
- idCVar net_verifyReliableQueue( "net_verifyReliableQueue", "2", CVAR_INTEGER, "0: warn only, 1: error, 2: fixup, 3: fixup and verbose, 4: force test" );
- #define RELIABLE_VERBOSE if ( net_verifyReliableQueue.GetInteger() >= 3 ) idLib::Printf
- void idPacketProcessor::VerifyEmptyReliableQueue( byte keepMsgBelowThis, byte replaceWithThisMsg ) {
- if ( net_verifyReliableQueue.GetInteger() == 4 ) {
- RELIABLE_VERBOSE( "pushing a fake game reliable\n" );
- const char * garbage = "garbage";
- QueueReliableMessage( keepMsgBelowThis + 4, (const byte *)garbage, 8 );
- QueueReliableMessage( replaceWithThisMsg, NULL, 0 );
- }
- if ( reliable.Num() == 0 ) {
- return;
- }
- if ( net_verifyReliableQueue.GetInteger() == 1 ) {
- idLib::Error( "reliable queue is not empty: %d messages", reliable.Num() );
- return;
- }
- idLib::Warning( "reliable queue is not empty: %d messages", reliable.Num() );
- if ( net_verifyReliableQueue.GetInteger() == 0 ) {
- return;
- }
- // drop some stuff that is potentially dangerous and should not transmit
- idDataQueue< MAX_RELIABLE_QUEUE, MAX_MSG_SIZE > clean;
- RELIABLE_VERBOSE( "rollback send sequence from %d to %d\n", reliableSequenceSend, reliable.ItemSequence( 0 ) );
- for ( int i = 0; i < reliable.Num(); i++ ) {
- byte peek = reliable.ItemData( i )[0];
- if ( peek < keepMsgBelowThis ) {
- RELIABLE_VERBOSE( "keeping %d\n", peek );
- clean.Append( reliable.ItemSequence( i ), reliable.ItemData( i ), reliable.ItemLength( i ) );
- } else {
- // Replace with fake msg, so we retain itemsequence ordering.
- // If we don't do this, it's possible we remove the last msg, then append a single msg before the next send,
- // and the client may think he already received the msg, since his last reliableSequenceRecv could be greater than our
- // reliableSequenceSend if he already received the group of reliables we are mucking with
- clean.Append( reliable.ItemSequence( i ), &replaceWithThisMsg, 1 );
- RELIABLE_VERBOSE( "dropping %d\n", peek );
- }
- }
- assert( reliable.Num() == clean.Num() );
- reliable = clean;
- }
|