mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-12-02 17:02:17 +00:00
1471 lines
40 KiB
C++
1471 lines
40 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
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_verboseSnapshot( "net_verboseSnapshot", "0", CVAR_INTEGER | CVAR_NOCHEAT, "Verbose snapshot code to help debug snapshot problems. Greater the number greater the spam" );
|
|
idCVar net_verboseSnapshotCompression( "net_verboseSnapshotCompression", "0", CVAR_INTEGER | CVAR_NOCHEAT, "Verbose snapshot code to help debug snapshot problems. Greater the number greater the spam" );
|
|
idCVar net_verboseSnapshotReport( "net_verboseSnapshotReport", "0", CVAR_INTEGER | CVAR_NOCHEAT, "Verbose snapshot code to help debug snapshot problems. Greater the number greater the spam" );
|
|
|
|
idCVar net_ssTemplateDebug( "net_ssTemplateDebug", "0", CVAR_BOOL, "Debug snapshot template states" );
|
|
idCVar net_ssTemplateDebug_len( "net_ssTemplateDebug_len", "32", CVAR_INTEGER, "Offset to start template state debugging" );
|
|
idCVar net_ssTemplateDebug_start( "net_ssTemplateDebug_start", "0", CVAR_INTEGER, "length of template state to print in debugging" );
|
|
|
|
/*
|
|
========================
|
|
InDebugRange
|
|
Helper function for net_ssTemplateDebug debugging
|
|
========================
|
|
*/
|
|
bool InDebugRange( int i )
|
|
{
|
|
return ( i >= net_ssTemplateDebug_start.GetInteger() && i < net_ssTemplateDebug_start.GetInteger() + net_ssTemplateDebug_len.GetInteger() );
|
|
}
|
|
/*
|
|
========================
|
|
PrintAlign
|
|
Helper function for net_ssTemplateDebug debugging
|
|
========================
|
|
*/
|
|
void PrintAlign( const char* text )
|
|
{
|
|
idLib::Printf( "%25s: 0x", text );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::objectState_t::Print
|
|
Helper function for net_ssTemplateDebug debugging
|
|
========================
|
|
*/
|
|
void idSnapShot::objectState_t::Print( const char* name )
|
|
{
|
|
|
|
unsigned int start = ( unsigned int )net_ssTemplateDebug_start.GetInteger();
|
|
unsigned int end = Min( ( unsigned int )buffer.Size(), start + net_ssTemplateDebug_len.GetInteger() );
|
|
|
|
PrintAlign( va( "%s: [sz %d]", name, buffer.Size() ) );
|
|
|
|
for( unsigned int i = start; i < end; i++ )
|
|
{
|
|
idLib::Printf( "%02X", buffer[i] );
|
|
}
|
|
idLib::Printf( "\n" );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::objectBuffer_t::Alloc
|
|
========================
|
|
*/
|
|
void idSnapShot::objectBuffer_t::Alloc( int s )
|
|
{
|
|
//assert( mem.IsMapHeap() );
|
|
if( !verify( s < SIZE_NOT_STALE ) )
|
|
{
|
|
idLib::FatalError( "s >= SIZE_NOT_STALE" );
|
|
}
|
|
_Release();
|
|
data = ( byte* )Mem_Alloc( s + 1, TAG_NETWORKING );
|
|
size = s;
|
|
data[size] = 1;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::objectBuffer_t::AddRef
|
|
========================
|
|
*/
|
|
void idSnapShot::objectBuffer_t::_AddRef()
|
|
{
|
|
if( data != NULL )
|
|
{
|
|
assert( size > 0 );
|
|
assert( data[size] < 255 );
|
|
data[size]++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::objectBuffer_t::Release
|
|
========================
|
|
*/
|
|
void idSnapShot::objectBuffer_t::_Release()
|
|
{
|
|
//assert( mem.IsMapHeap() );
|
|
if( data != NULL )
|
|
{
|
|
assert( size > 0 );
|
|
if( --data[size] == 0 )
|
|
{
|
|
Mem_Free( data );
|
|
}
|
|
data = NULL;
|
|
size = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::objectBuffer_t::operator=
|
|
========================
|
|
*/
|
|
void idSnapShot::objectBuffer_t::operator=( const idSnapShot::objectBuffer_t& other )
|
|
{
|
|
//assert( mem.IsMapHeap() );
|
|
if( this != &other )
|
|
{
|
|
_Release();
|
|
data = other.data;
|
|
size = other.size;
|
|
_AddRef();
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::idSnapShot
|
|
========================
|
|
*/
|
|
idSnapShot::idSnapShot() :
|
|
time( 0 ),
|
|
recvTime( 0 )
|
|
{
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::idSnapShot
|
|
========================
|
|
*/
|
|
idSnapShot::idSnapShot( const idSnapShot& other ) : time( 0 ), recvTime( 0 )
|
|
{
|
|
*this = other;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::~idSnapShot
|
|
========================
|
|
*/
|
|
idSnapShot::~idSnapShot()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::Clear
|
|
========================
|
|
*/
|
|
void idSnapShot::Clear()
|
|
{
|
|
time = 0;
|
|
recvTime = 0;
|
|
for( int i = 0; i < objectStates.Num(); i++ )
|
|
{
|
|
FreeObjectState( i );
|
|
}
|
|
objectStates.Clear();
|
|
allocatedObjs.Shutdown();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::operator=
|
|
========================
|
|
*/
|
|
void idSnapShot::operator=( const idSnapShot& other )
|
|
{
|
|
//assert( mem.IsMapHeap() );
|
|
|
|
if( this != &other )
|
|
{
|
|
for( int i = other.objectStates.Num(); i < objectStates.Num(); i++ )
|
|
{
|
|
FreeObjectState( i );
|
|
}
|
|
objectStates.AssureSize( other.objectStates.Num(), NULL );
|
|
for( int i = 0; i < objectStates.Num(); i++ )
|
|
{
|
|
const objectState_t& otherState = *other.objectStates[i];
|
|
if( objectStates[i] == NULL )
|
|
{
|
|
objectStates[i] = allocatedObjs.Alloc();
|
|
}
|
|
objectState_t& state = *objectStates[i];
|
|
state.objectNum = otherState.objectNum;
|
|
state.buffer = otherState.buffer;
|
|
state.visMask = otherState.visMask;
|
|
state.stale = otherState.stale;
|
|
state.deleted = otherState.deleted;
|
|
state.changedCount = otherState.changedCount;
|
|
state.expectedSequence = otherState.expectedSequence;
|
|
state.createdFromTemplate = otherState.createdFromTemplate;
|
|
}
|
|
time = other.time;
|
|
recvTime = other.recvTime;
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::PeekDeltaSequence
|
|
========================
|
|
*/
|
|
void idSnapShot::PeekDeltaSequence( const char* deltaMem, int deltaSize, int& sequence, int& baseSequence )
|
|
{
|
|
lzwCompressionData_t lzwData;
|
|
idLZWCompressor lzwCompressor( &lzwData );
|
|
|
|
lzwCompressor.Start( ( uint8* )deltaMem, deltaSize );
|
|
lzwCompressor.ReadAgnostic( sequence );
|
|
lzwCompressor.ReadAgnostic( baseSequence );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::ReadDeltaForJob
|
|
========================
|
|
*/
|
|
bool idSnapShot::ReadDeltaForJob( const char* deltaMem, int deltaSize, int visIndex, idSnapShot* templateStates )
|
|
{
|
|
|
|
bool report = net_verboseSnapshotReport.GetBool();
|
|
net_verboseSnapshotReport.SetBool( false );
|
|
|
|
lzwCompressionData_t lzwData;
|
|
idZeroRunLengthCompressor rleCompressor;
|
|
idLZWCompressor lzwCompressor( &lzwData );
|
|
int bytesRead = 0; // how many uncompressed bytes we read in. Used to figure out compression ratio
|
|
|
|
lzwCompressor.Start( ( uint8* )deltaMem, deltaSize );
|
|
|
|
// Skip past sequence and baseSequence
|
|
int sequence = 0;
|
|
int baseSequence = 0;
|
|
|
|
lzwCompressor.ReadAgnostic( sequence );
|
|
lzwCompressor.ReadAgnostic( baseSequence );
|
|
lzwCompressor.ReadAgnostic( time );
|
|
bytesRead += sizeof( int ) * 3;
|
|
|
|
int objectNum = 0;
|
|
uint16 delta = 0;
|
|
|
|
|
|
while( lzwCompressor.ReadAgnostic( delta, true ) == sizeof( delta ) )
|
|
{
|
|
bytesRead += sizeof( delta );
|
|
|
|
objectNum += delta;
|
|
if( objectNum >= 0xFFFF )
|
|
{
|
|
// full delta
|
|
if( net_verboseSnapshotCompression.GetBool() )
|
|
{
|
|
float compRatio = static_cast<float>( deltaSize ) / static_cast<float>( bytesRead );
|
|
idLib::Printf( "Snapshot (%d/%d). ReadSize: %d DeltaSize: %d Ratio: %.3f\n", sequence, baseSequence, bytesRead, deltaSize, compRatio );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
objectState_t& state = FindOrCreateObjectByID( objectNum );
|
|
|
|
objectSize_t newsize = 0;
|
|
lzwCompressor.ReadAgnostic( newsize );
|
|
bytesRead += sizeof( newsize );
|
|
|
|
if( newsize == SIZE_STALE )
|
|
{
|
|
NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d goes stale\n", objectNum );
|
|
// sanity
|
|
bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0;
|
|
if( !oldVisible )
|
|
{
|
|
NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected already stale\n" );
|
|
}
|
|
state.visMask &= ~( 1 << visIndex );
|
|
state.stale = true;
|
|
// We need to make sure we haven't freed stale objects.
|
|
assert( state.buffer.Size() > 0 );
|
|
// no more data
|
|
continue;
|
|
}
|
|
else if( newsize == SIZE_NOT_STALE )
|
|
{
|
|
NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d no longer stale\n", objectNum );
|
|
// sanity
|
|
bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0;
|
|
if( oldVisible )
|
|
{
|
|
NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected not stale\n" );
|
|
}
|
|
state.visMask |= ( 1 << visIndex );
|
|
state.stale = false;
|
|
// the latest state is packed in, get the new size and continue reading the new state
|
|
lzwCompressor.ReadAgnostic( newsize );
|
|
bytesRead += sizeof( newsize );
|
|
}
|
|
|
|
objectState_t* objTemplateState = templateStates->FindObjectByID( objectNum );
|
|
|
|
if( newsize == 0 )
|
|
{
|
|
// object deleted: reset state now so next one to use it doesn't have old data
|
|
state.deleted = false;
|
|
state.stale = false;
|
|
state.changedCount = 0;
|
|
state.expectedSequence = 0;
|
|
state.visMask = 0;
|
|
state.buffer._Release();
|
|
state.createdFromTemplate = false;
|
|
|
|
if( objTemplateState != NULL && objTemplateState->buffer.Size() && objTemplateState->expectedSequence < baseSequence )
|
|
{
|
|
idLib::PrintfIf( net_ssTemplateDebug.GetBool(), "Clearing old template state[%d] [%d<%d]\n", objectNum, objTemplateState->expectedSequence, baseSequence );
|
|
objTemplateState->deleted = false;
|
|
objTemplateState->stale = false;
|
|
objTemplateState->changedCount = 0;
|
|
objTemplateState->expectedSequence = 0;
|
|
objTemplateState->visMask = 0;
|
|
objTemplateState->buffer._Release();
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
// new state?
|
|
bool debug = false;
|
|
if( state.buffer.Size() == 0 )
|
|
{
|
|
state.createdFromTemplate = true;
|
|
// Brand new state
|
|
if( objTemplateState != NULL && objTemplateState->buffer.Size() > 0 && sequence >= objTemplateState->expectedSequence )
|
|
{
|
|
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 );
|
|
state.buffer = objTemplateState->buffer;
|
|
|
|
if( net_ssTemplateDebug.GetBool() )
|
|
{
|
|
state.Print( "SPAWN STATE" );
|
|
debug = true;
|
|
PrintAlign( "DELTA STATE" );
|
|
}
|
|
}
|
|
else if( net_ssTemplateDebug.GetBool() )
|
|
{
|
|
idLib::Printf( "\nNew snapobject[%d] in snapshot %d/%d but no basestate found locally so creating new\n", objectNum, sequence, baseSequence );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
state.createdFromTemplate = false;
|
|
}
|
|
|
|
// the buffer shrank or stayed the same
|
|
objectBuffer_t newbuffer( newsize );
|
|
rleCompressor.Start( NULL, &lzwCompressor, newsize );
|
|
objectSize_t compareSize = Min( state.buffer.Size(), newsize );
|
|
for( objectSize_t i = 0; i < compareSize; i++ )
|
|
{
|
|
byte b = rleCompressor.ReadByte();
|
|
newbuffer[i] = state.buffer[i] + b;
|
|
|
|
if( debug && InDebugRange( i ) )
|
|
{
|
|
idLib::Printf( "%02X", b );
|
|
}
|
|
}
|
|
// Catch leftover
|
|
if( newsize > compareSize )
|
|
{
|
|
rleCompressor.ReadBytes( newbuffer.Ptr() + compareSize, newsize - compareSize );
|
|
|
|
if( debug )
|
|
{
|
|
for( objectSize_t i = compareSize; i < newsize; i++ )
|
|
{
|
|
if( InDebugRange( i ) )
|
|
{
|
|
idLib::Printf( "%02X", newbuffer[i] );
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
state.buffer = newbuffer;
|
|
state.changedCount = sequence;
|
|
bytesRead += sizeof( byte ) * newsize;
|
|
if( debug )
|
|
{
|
|
idLib::Printf( "\n" );
|
|
state.Print( "NEW STATE" );
|
|
}
|
|
|
|
if( report )
|
|
{
|
|
idLib::Printf( " Obj %d Compressed: Size %d \n", objectNum, rleCompressor.CompressedSize() );
|
|
}
|
|
}
|
|
#ifdef SNAPSHOT_CHECKSUMS
|
|
extern uint32 SnapObjChecksum( const uint8 * data, int length );
|
|
if( state.buffer.Size() > 0 )
|
|
{
|
|
uint32 checksum = 0;
|
|
lzwCompressor.ReadAgnostic( checksum );
|
|
bytesRead += sizeof( checksum );
|
|
if( !verify( checksum == SnapObjChecksum( state.buffer.Ptr(), state.buffer.Size() ) ) )
|
|
{
|
|
idLib::Error( " Invalid snapshot checksum" );
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
// partial delta
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::SubmitObjectJob
|
|
========================
|
|
*/
|
|
|
|
void idSnapShot::SubmitObjectJob( const submitDeltaJobsInfo_t& submitDeltaJobsInfo,
|
|
objectState_t* newState,
|
|
objectState_t* oldState,
|
|
objParms_t*& baseObjParm,
|
|
objParms_t*& curObjParm,
|
|
objHeader_t*& curHeader,
|
|
uint8*& curObjDest,
|
|
lzwParm_t*& curlzwParm
|
|
)
|
|
{
|
|
assert( newState != NULL || oldState != NULL );
|
|
assert_16_byte_aligned( curHeader );
|
|
assert_16_byte_aligned( curObjDest );
|
|
|
|
int32 dataSize = newState != NULL ? newState->buffer.Size() : 0;
|
|
int totalSize = OBJ_DEST_SIZE_ALIGN16( dataSize );
|
|
|
|
if( curObjParm - submitDeltaJobsInfo.objParms >= submitDeltaJobsInfo.maxObjParms )
|
|
{
|
|
idLib::Error( "Out of parms for snapshot jobs.\n" );
|
|
}
|
|
|
|
// Check to see if we are out of dest write space, and need to flush the jobs
|
|
bool needToSubmit = ( curObjDest - submitDeltaJobsInfo.objMemory ) + totalSize >= submitDeltaJobsInfo.maxObjMemory;
|
|
needToSubmit |= ( curHeader - submitDeltaJobsInfo.headers >= submitDeltaJobsInfo.maxHeaders );
|
|
|
|
if( needToSubmit )
|
|
{
|
|
// If this obj will put us over the limit, then submit the jobs now, and start over re-using the same buffers
|
|
SubmitLZWJob( submitDeltaJobsInfo, baseObjParm, curObjParm, curlzwParm, true );
|
|
curHeader = submitDeltaJobsInfo.headers;
|
|
curObjDest = submitDeltaJobsInfo.objMemory;
|
|
}
|
|
|
|
// Setup obj parms
|
|
assert( submitDeltaJobsInfo.visIndex < 256 );
|
|
curObjParm->visIndex = submitDeltaJobsInfo.visIndex;
|
|
curObjParm->destHeader = curHeader;
|
|
curObjParm->dest = curObjDest;
|
|
|
|
memset( &curObjParm->newState, 0, sizeof( curObjParm->newState ) );
|
|
memset( &curObjParm->oldState, 0, sizeof( curObjParm->oldState ) );
|
|
|
|
if( newState != NULL )
|
|
{
|
|
assert( newState->buffer.Size() <= 65535 );
|
|
|
|
curObjParm->newState.valid = 1;
|
|
curObjParm->newState.data = newState->buffer.Ptr();
|
|
curObjParm->newState.size = newState->buffer.Size();
|
|
curObjParm->newState.objectNum = newState->objectNum;
|
|
curObjParm->newState.visMask = newState->visMask;
|
|
}
|
|
|
|
if( oldState != NULL )
|
|
{
|
|
assert( oldState->buffer.Size() <= 65535 );
|
|
|
|
curObjParm->oldState.valid = 1;
|
|
curObjParm->oldState.data = oldState->buffer.Ptr();
|
|
curObjParm->oldState.size = oldState->buffer.Size();
|
|
curObjParm->oldState.objectNum = oldState->objectNum;
|
|
curObjParm->oldState.visMask = oldState->visMask;
|
|
}
|
|
|
|
assert_16_byte_aligned( curObjParm );
|
|
assert_16_byte_aligned( curObjParm->newState.data );
|
|
assert_16_byte_aligned( curObjParm->oldState.data );
|
|
|
|
SnapshotObjectJob( curObjParm );
|
|
|
|
// Advance past header + data
|
|
curObjDest += totalSize;
|
|
|
|
// Advance parm pointer
|
|
curObjParm++;
|
|
|
|
// Advance header pointer
|
|
curHeader++;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::SubmitLZWJob
|
|
Take the current list of delta'd + zlre processed objects, and write them to the lzw stream.
|
|
========================
|
|
*/
|
|
void idSnapShot::SubmitLZWJob(
|
|
const submitDeltaJobsInfo_t& writeDeltaInfo,
|
|
objParms_t*& baseObjParm, // Pointer to the first obj parm for the current stream
|
|
objParms_t*& curObjParm, // Current obj parm
|
|
lzwParm_t*& curlzwParm, // Current delta parm
|
|
bool saveDictionary
|
|
)
|
|
{
|
|
int numObjects = curObjParm - baseObjParm;
|
|
|
|
if( numObjects == 0 )
|
|
{
|
|
return; // Nothing to do
|
|
}
|
|
|
|
if( curlzwParm - writeDeltaInfo.lzwParms >= writeDeltaInfo.maxDeltaParms )
|
|
{
|
|
idLib::Error( "SubmitLZWJob: Not enough lzwParams.\n" );
|
|
return; // Can't do anymore
|
|
}
|
|
|
|
curlzwParm->numObjects = numObjects;
|
|
curlzwParm->headers = writeDeltaInfo.headers; // We always start grabbing from the beggining of the memory (it's reused, with fences to protect memory sharing)
|
|
curlzwParm->curTime = this->GetTime();
|
|
curlzwParm->baseTime = writeDeltaInfo.oldSnap->GetTime();
|
|
curlzwParm->baseSequence = writeDeltaInfo.baseSequence;
|
|
curlzwParm->fragmented = ( curlzwParm != writeDeltaInfo.lzwParms );
|
|
curlzwParm->saveDictionary = saveDictionary;
|
|
|
|
curlzwParm->ioData = writeDeltaInfo.lzwInOutData;
|
|
|
|
LZWJob( curlzwParm );
|
|
|
|
curlzwParm++;
|
|
|
|
// Set base so it now points to where the parms start for the new stream
|
|
baseObjParm = curObjParm;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::GetTemplateState
|
|
Helper function for getting template objectState.
|
|
newState parameter is optional and is just used for debugging/printf comparison of the template and actual state
|
|
========================
|
|
*/
|
|
idSnapShot::objectState_t* idSnapShot::GetTemplateState( int objNum, idSnapShot* templateStates, idSnapShot::objectState_t* newState /*=NULL*/ )
|
|
{
|
|
objectState_t* oldState = NULL;
|
|
int spawnedStateIndex = templateStates->FindObjectIndexByID( objNum );
|
|
if( spawnedStateIndex >= 0 )
|
|
{
|
|
oldState = templateStates->objectStates[ spawnedStateIndex ];
|
|
|
|
if( net_ssTemplateDebug.GetBool() )
|
|
{
|
|
idLib::Printf( "\nGetTemplateState[%d]\n", objNum );
|
|
oldState->Print( "SPAWN STATE" );
|
|
if( newState != NULL )
|
|
{
|
|
newState->Print( "CUR STATE" );
|
|
}
|
|
}
|
|
}
|
|
return oldState;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::SubmitWriteDeltaToJobs
|
|
========================
|
|
*/
|
|
void idSnapShot::SubmitWriteDeltaToJobs( const submitDeltaJobsInfo_t& submitDeltaJobInfo )
|
|
{
|
|
objParms_t* curObjParms = submitDeltaJobInfo.objParms;
|
|
objParms_t* baseObjParms = submitDeltaJobInfo.objParms;
|
|
lzwParm_t* curlzwParms = submitDeltaJobInfo.lzwParms;
|
|
objHeader_t* curHeader = submitDeltaJobInfo.headers;
|
|
uint8* curObjMemory = submitDeltaJobInfo.objMemory;
|
|
|
|
submitDeltaJobInfo.lzwInOutData->numlzwDeltas = 0;
|
|
submitDeltaJobInfo.lzwInOutData->lzwBytes = 0;
|
|
submitDeltaJobInfo.lzwInOutData->fullSnap = false;
|
|
|
|
int j = 0;
|
|
|
|
int numOldStates = submitDeltaJobInfo.oldSnap->objectStates.Num();
|
|
|
|
for( int i = 0; i < objectStates.Num(); i++ )
|
|
{
|
|
objectState_t& newState = *objectStates[i];
|
|
if( !verify( newState.buffer.Size() > 0 ) )
|
|
{
|
|
// 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
|
|
// off delta compression, eventually resulting in a checksum error that is a pain to track down.
|
|
idLib::Warning( "Snap obj [%d] state.size <= 0... skipping ", newState.objectNum );
|
|
continue;
|
|
}
|
|
|
|
if( j >= numOldStates )
|
|
{
|
|
// We no longer have old objects to compare to.
|
|
// All objects are new from this point on.
|
|
|
|
objectState_t* oldState = GetTemplateState( newState.objectNum, submitDeltaJobInfo.templateStates, &newState );
|
|
SubmitObjectJob( submitDeltaJobInfo, &newState, oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms );
|
|
continue;
|
|
}
|
|
|
|
// write any deleted entities up to this one
|
|
for( ; j < numOldStates && newState.objectNum > submitDeltaJobInfo.oldSnap->objectStates[j]->objectNum; j++ )
|
|
{
|
|
objectState_t& oldState = *submitDeltaJobInfo.oldSnap->objectStates[j];
|
|
|
|
if( ( oldState.stale && !oldState.deleted ) || oldState.buffer.Size() <= 0 )
|
|
{
|
|
continue; // Don't delete objects that are stale and not marked as deleted
|
|
}
|
|
|
|
SubmitObjectJob( submitDeltaJobInfo, NULL, &oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms );
|
|
}
|
|
|
|
if( j >= numOldStates )
|
|
{
|
|
continue; // Went past end of old list deleting objects
|
|
}
|
|
|
|
// Beyond this point, we may have old state to compare against
|
|
objectState_t& submittedOldState = *submitDeltaJobInfo.oldSnap->objectStates[j];
|
|
objectState_t* oldState = &submittedOldState;
|
|
|
|
if( newState.objectNum == oldState->objectNum )
|
|
{
|
|
if( oldState->buffer.Size() == 0 )
|
|
{
|
|
// New state (even though snapObj existed, its size was zero)
|
|
oldState = GetTemplateState( newState.objectNum, submitDeltaJobInfo.templateStates, &newState );
|
|
}
|
|
|
|
SubmitObjectJob( submitDeltaJobInfo, &newState, oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms );
|
|
j++;
|
|
}
|
|
else
|
|
{
|
|
// Different object, this one is new,
|
|
// Spawned
|
|
oldState = GetTemplateState( newState.objectNum, submitDeltaJobInfo.templateStates, &newState );
|
|
SubmitObjectJob( submitDeltaJobInfo, &newState, oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms );
|
|
}
|
|
}
|
|
// Finally, remove any entities at the end
|
|
for( ; j < submitDeltaJobInfo.oldSnap->objectStates.Num(); j++ )
|
|
{
|
|
objectState_t& oldState = *submitDeltaJobInfo.oldSnap->objectStates[j];
|
|
|
|
if( ( oldState.stale && !oldState.deleted ) || oldState.buffer.Size() <= 0 )
|
|
{
|
|
continue; // Don't delete objects that are stale and not marked as deleted
|
|
}
|
|
|
|
SubmitObjectJob( submitDeltaJobInfo, NULL, &oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms );
|
|
}
|
|
|
|
// Submit any objects that are left over (will be all if they all fit up to this point)
|
|
SubmitLZWJob( submitDeltaJobInfo, baseObjParms, curObjParms, curlzwParms, false );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::ReadDelta
|
|
========================
|
|
*/
|
|
bool idSnapShot::ReadDelta( idFile* file, int visIndex )
|
|
{
|
|
|
|
file->ReadBig( time );
|
|
|
|
int objectNum = 0;
|
|
uint16 delta = 0;
|
|
while( file->ReadBig( delta ) == sizeof( delta ) )
|
|
{
|
|
objectNum += delta;
|
|
if( objectNum >= 0xFFFF )
|
|
{
|
|
// full delta
|
|
return true;
|
|
}
|
|
objectState_t& state = FindOrCreateObjectByID( objectNum );
|
|
objectSize_t newsize = 0;
|
|
file->ReadBig( newsize );
|
|
|
|
if( newsize == SIZE_STALE )
|
|
{
|
|
NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d goes stale\n", objectNum );
|
|
// sanity
|
|
bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0;
|
|
if( !oldVisible )
|
|
{
|
|
NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected already stale\n" );
|
|
}
|
|
state.visMask &= ~( 1 << visIndex );
|
|
state.stale = true;
|
|
// We need to make sure we haven't freed stale objects.
|
|
assert( state.buffer.Size() > 0 );
|
|
// no more data
|
|
continue;
|
|
}
|
|
else if( newsize == SIZE_NOT_STALE )
|
|
{
|
|
NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d no longer stale\n", objectNum );
|
|
// sanity
|
|
bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0;
|
|
if( oldVisible )
|
|
{
|
|
NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected not stale\n" );
|
|
}
|
|
state.visMask |= ( 1 << visIndex );
|
|
state.stale = false;
|
|
// the latest state is packed in, get the new size and continue reading the new state
|
|
file->ReadBig( newsize );
|
|
}
|
|
|
|
if( newsize == 0 )
|
|
{
|
|
// object deleted
|
|
state.buffer._Release();
|
|
}
|
|
else
|
|
{
|
|
objectBuffer_t newbuffer( newsize );
|
|
objectSize_t compareSize = Min( newsize, state.buffer.Size() );
|
|
|
|
for( objectSize_t i = 0; i < compareSize; i++ )
|
|
{
|
|
uint8 delta = 0;
|
|
file->ReadBig<byte>( delta );
|
|
newbuffer[i] = state.buffer[i] + delta;
|
|
}
|
|
|
|
if( newsize > compareSize )
|
|
{
|
|
file->Read( newbuffer.Ptr() + compareSize, newsize - compareSize );
|
|
}
|
|
|
|
state.buffer = newbuffer;
|
|
state.changedCount++;
|
|
}
|
|
|
|
#ifdef SNAPSHOT_CHECKSUMS
|
|
if( state.buffer.Size() > 0 )
|
|
{
|
|
unsigned int checksum = 0;
|
|
file->ReadBig( checksum );
|
|
assert( checksum == MD5_BlockChecksum( state.buffer.Ptr(), state.buffer.Size() ) );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// partial delta
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::WriteObject
|
|
========================
|
|
*/
|
|
void idSnapShot::WriteObject( idFile* file, int visIndex, objectState_t* newState, objectState_t* oldState, int& lastobjectNum )
|
|
{
|
|
assert( newState != NULL || oldState != NULL );
|
|
|
|
bool visChange = false; // visibility changes will be signified with a 0xffff state size
|
|
bool visSendState = false; // the state is sent when an entity is no longer stale
|
|
|
|
// Compute visibility changes
|
|
// (we need to do this before writing out object id, because we may not need to write out the id if we early out)
|
|
// (when we don't write out the id, we assume this is an "ack" when we deserialize the objects)
|
|
if( newState != NULL && oldState != NULL )
|
|
{
|
|
// Check visibility
|
|
assert( newState->objectNum == oldState->objectNum );
|
|
|
|
if( visIndex > 0 )
|
|
{
|
|
bool oldVisible = ( oldState->visMask & ( 1 << visIndex ) ) != 0;
|
|
bool newVisible = ( newState->visMask & ( 1 << visIndex ) ) != 0;
|
|
|
|
// Force visible if we need to either create or destroy this object
|
|
newVisible |= ( newState->buffer.Size() == 0 ) != ( oldState->buffer.Size() == 0 );
|
|
|
|
if( !oldVisible && !newVisible )
|
|
{
|
|
// object is stale and ack'ed for this client, write nothing (see 'same object' below)
|
|
return;
|
|
}
|
|
else if( oldVisible && !newVisible )
|
|
{
|
|
NET_VERBOSESNAPSHOT_PRINT( "object %d to client %d goes stale\n", newState->objectNum, visIndex );
|
|
visChange = true;
|
|
visSendState = false;
|
|
}
|
|
else if( !oldVisible && newVisible )
|
|
{
|
|
NET_VERBOSESNAPSHOT_PRINT( "object %d to client %d no longer stale\n", newState->objectNum, visIndex );
|
|
visChange = true;
|
|
visSendState = true;
|
|
}
|
|
}
|
|
|
|
// Same object, write a delta (never early out during vis changes)
|
|
if( !visChange && newState->buffer.Size() == oldState->buffer.Size() &&
|
|
( ( newState->buffer.Ptr() == oldState->buffer.Ptr() ) || memcmp( newState->buffer.Ptr(), oldState->buffer.Ptr(), newState->buffer.Size() ) == 0 ) )
|
|
{
|
|
// same state, write nothing
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Get the id of the object we are writing out
|
|
uint16 objectNum;
|
|
if( newState != NULL )
|
|
{
|
|
objectNum = newState->objectNum;
|
|
}
|
|
else if( oldState != NULL )
|
|
{
|
|
objectNum = oldState->objectNum;
|
|
}
|
|
else
|
|
{
|
|
objectNum = 0;
|
|
}
|
|
|
|
assert( objectNum == 0 || objectNum > lastobjectNum );
|
|
|
|
// Write out object id (using delta)
|
|
uint16 objectDelta = objectNum - lastobjectNum;
|
|
file->WriteBig( objectDelta );
|
|
lastobjectNum = objectNum;
|
|
|
|
if( newState == NULL )
|
|
{
|
|
// Deleted, write 0 size
|
|
assert( oldState != NULL );
|
|
file->WriteBig<objectSize_t>( 0 );
|
|
}
|
|
else if( oldState == NULL )
|
|
{
|
|
// New object, write out full state
|
|
assert( newState != NULL );
|
|
// delta against an empty snap
|
|
file->WriteBig( newState->buffer.Size() );
|
|
file->Write( newState->buffer.Ptr(), newState->buffer.Size() );
|
|
}
|
|
else
|
|
{
|
|
// Compare to last object
|
|
assert( newState != NULL && oldState != NULL );
|
|
assert( newState->objectNum == oldState->objectNum );
|
|
|
|
if( visChange )
|
|
{
|
|
// fake size indicates vis state change
|
|
// NOTE: we may still send a real size and a state below, for 'no longer stale' transitions
|
|
// TMP: send 0xFFFF for going stale and 0xFFFF - 1 for no longer stale
|
|
file->WriteBig<objectSize_t>( visSendState ? SIZE_NOT_STALE : SIZE_STALE );
|
|
}
|
|
if( !visChange || visSendState )
|
|
{
|
|
|
|
objectSize_t compareSize = Min( newState->buffer.Size(), oldState->buffer.Size() ); // Get the number of bytes that overlap
|
|
|
|
file->WriteBig( newState->buffer.Size() ); // Write new size
|
|
|
|
// Compare bytes that overlap
|
|
for( objectSize_t b = 0; b < compareSize; b++ )
|
|
{
|
|
file->WriteBig<byte>( ( 0xFF + 1 + newState->buffer[b] - oldState->buffer[b] ) & 0xFF );
|
|
}
|
|
|
|
// Write leftover
|
|
if( newState->buffer.Size() > compareSize )
|
|
{
|
|
file->Write( newState->buffer.Ptr() + oldState->buffer.Size(), newState->buffer.Size() - compareSize );
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef SNAPSHOT_CHECKSUMS
|
|
if( ( !visChange || visSendState ) && newState != NULL )
|
|
{
|
|
assert( newState->buffer.Size() > 0 );
|
|
unsigned int checksum = MD5_BlockChecksum( newState->buffer.Ptr(), newState->buffer.Size() );
|
|
file->WriteBig( checksum );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::PrintReport
|
|
========================
|
|
*/
|
|
void idSnapShot::PrintReport()
|
|
{
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::WriteDelta
|
|
========================
|
|
*/
|
|
bool idSnapShot::WriteDelta( idSnapShot& old, int visIndex, idFile* file, int maxLength, int optimalLength )
|
|
{
|
|
file->WriteBig( time );
|
|
|
|
int objectHeaderSize = sizeof( uint16 ) + sizeof( objectSize_t );
|
|
#ifdef SNAPSHOT_CHECKSUMS
|
|
objectHeaderSize += sizeof( unsigned int );
|
|
#endif
|
|
|
|
int lastobjectNum = 0;
|
|
int j = 0;
|
|
|
|
for( int i = 0; i < objectStates.Num(); i++ )
|
|
{
|
|
objectState_t& newState = *objectStates[i];
|
|
|
|
if( optimalLength > 0 && file->Length() >= optimalLength )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( !verify( newState.buffer.Size() < maxLength ) )
|
|
{
|
|
// If the new state's size is > the max packet size, we'll never be able to send it!
|
|
idLib::Warning( "Snap obj [%d] state.size > max packet length. Skipping... ", newState.objectNum );
|
|
continue;
|
|
}
|
|
else if( !verify( newState.buffer.Size() > 0 ) )
|
|
{
|
|
// 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
|
|
// off delta compression, eventually resulting in a checksum error that is a pain to track down.
|
|
idLib::Warning( "Snap obj [%d] state.size <= 0... skipping ", newState.objectNum );
|
|
continue;
|
|
}
|
|
|
|
if( file->Length() + objectHeaderSize + newState.buffer.Size() >= maxLength )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( j >= old.objectStates.Num() )
|
|
{
|
|
// delta against an empty snap
|
|
WriteObject( file, visIndex, &newState, NULL, lastobjectNum );
|
|
continue;
|
|
}
|
|
|
|
// write any deleted entities up to this one
|
|
for( ; newState.objectNum > old.objectStates[j]->objectNum; j++ )
|
|
{
|
|
if( file->Length() + objectHeaderSize >= maxLength )
|
|
{
|
|
return false;
|
|
}
|
|
objectState_t& oldState = *old.objectStates[j];
|
|
WriteObject( file, visIndex, NULL, &oldState, lastobjectNum );
|
|
}
|
|
|
|
// Beyond this point, we have old state to compare against
|
|
objectState_t& oldState = *old.objectStates[j];
|
|
|
|
if( newState.objectNum == oldState.objectNum )
|
|
{
|
|
// FIXME: We don't need to early out if WriteObject determines that we won't send the object due to being stale
|
|
if( file->Length() + objectHeaderSize + newState.buffer.Size() >= maxLength )
|
|
{
|
|
return false;
|
|
}
|
|
WriteObject( file, visIndex, &newState, &oldState, lastobjectNum );
|
|
j++;
|
|
}
|
|
else
|
|
{
|
|
if( file->Length() + objectHeaderSize + newState.buffer.Size() >= maxLength )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Different object, this one is new, write the full state
|
|
WriteObject( file, visIndex, &newState, NULL, lastobjectNum );
|
|
}
|
|
}
|
|
// Finally, remove any entities at the end
|
|
for( ; j < old.objectStates.Num(); j++ )
|
|
{
|
|
if( file->Length() + objectHeaderSize >= maxLength )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( optimalLength > 0 && file->Length() >= optimalLength )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
objectState_t& oldState = *old.objectStates[j];
|
|
WriteObject( file, visIndex, NULL, &oldState, lastobjectNum );
|
|
}
|
|
if( file->Length() + 2 >= maxLength )
|
|
{
|
|
return false;
|
|
}
|
|
uint16 objectDelta = 0xFFFF - lastobjectNum;
|
|
file->WriteBig( objectDelta );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::AddObject
|
|
========================
|
|
*/
|
|
idSnapShot::objectState_t* idSnapShot::S_AddObject( int objectNum, uint32 visMask, const char* data, int _size, const char* tag )
|
|
{
|
|
objectSize_t size = _size;
|
|
objectState_t& state = FindOrCreateObjectByID( objectNum );
|
|
state.visMask = visMask;
|
|
if( state.buffer.Size() == size && state.buffer.NumRefs() == 1 )
|
|
{
|
|
// re-use the same buffer
|
|
memcpy( state.buffer.Ptr(), data, size );
|
|
}
|
|
else
|
|
{
|
|
objectBuffer_t buffer( size );
|
|
memcpy( buffer.Ptr(), data, size );
|
|
state.buffer = buffer;
|
|
}
|
|
return &state;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::CopyObject
|
|
========================
|
|
*/
|
|
|
|
bool idSnapShot::CopyObject( const idSnapShot& oldss, int objectNum, bool forceStale )
|
|
{
|
|
|
|
int oldIndex = oldss.FindObjectIndexByID( objectNum );
|
|
if( oldIndex == -1 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const objectState_t& oldState = *oldss.objectStates[oldIndex];
|
|
objectState_t& newState = FindOrCreateObjectByID( objectNum );
|
|
|
|
newState.buffer = oldState.buffer;
|
|
newState.visMask = oldState.visMask;
|
|
newState.stale = oldState.stale;
|
|
newState.deleted = oldState.deleted;
|
|
newState.changedCount = oldState.changedCount;
|
|
newState.expectedSequence = oldState.expectedSequence;
|
|
newState.createdFromTemplate = oldState.createdFromTemplate;
|
|
|
|
if( forceStale )
|
|
{
|
|
newState.visMask = 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::CompareObject
|
|
start, end, and oldStart can optionally be passed in to compare subsections of the object
|
|
default parameters will compare entire object
|
|
========================
|
|
*/
|
|
int idSnapShot::CompareObject( const idSnapShot* oldss, int objectNum, int start, int end, int oldStart )
|
|
{
|
|
if( oldss == NULL )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
assert( FindObjectIndexByID( objectNum ) >= 0 );
|
|
|
|
objectState_t& newState = FindOrCreateObjectByID( objectNum );
|
|
|
|
int oldIndex = oldss->FindObjectIndexByID( objectNum );
|
|
if( oldIndex == -1 )
|
|
{
|
|
return ( end == 0 ? newState.buffer.Size() : end - start ); // Didn't exist in the old state, so we take the hit on the entire size
|
|
}
|
|
|
|
objectState_t& oldState = const_cast< objectState_t& >( *oldss->objectStates[oldIndex] );
|
|
|
|
int bytes = 0;
|
|
|
|
int oldOffset = oldStart - start;
|
|
int commonSize = ( newState.buffer.Size() <= oldState.buffer.Size() - oldOffset ) ? newState.buffer.Size() : oldState.buffer.Size() - oldOffset;
|
|
if( end == 0 )
|
|
{
|
|
// default 0 means compare the whole thing
|
|
end = commonSize;
|
|
|
|
// Get leftover (if any)
|
|
bytes = ( newState.buffer.Size() > oldState.buffer.Size() ) ? ( newState.buffer.Size() - oldState.buffer.Size() ) : 0;
|
|
}
|
|
else
|
|
{
|
|
|
|
// else only compare up to end or the max buffer and dont include leftover
|
|
end = Min( commonSize, end );
|
|
}
|
|
|
|
for( int b = start; b < end; b++ )
|
|
{
|
|
if( verify( b >= 0 && b < ( int )newState.buffer.Size() && b + oldOffset >= 0 && b + oldOffset < ( int )oldState.buffer.Size() ) )
|
|
{
|
|
bytes += ( newState.buffer[b] != oldState.buffer[b + oldOffset] ) ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::GetObjectMsgByIndex
|
|
========================
|
|
*/
|
|
int idSnapShot::GetObjectMsgByIndex( int i, idBitMsg& msg, bool ignoreIfStale ) const
|
|
{
|
|
if( i < 0 || i >= objectStates.Num() )
|
|
{
|
|
return -1;
|
|
}
|
|
objectState_t& state = *objectStates[i];
|
|
if( state.stale && ignoreIfStale )
|
|
{
|
|
return -1;
|
|
}
|
|
msg.InitRead( state.buffer.Ptr(), state.buffer.Size() );
|
|
return state.objectNum;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::ObjectIsStaleByIndex
|
|
========================
|
|
*/
|
|
bool idSnapShot::ObjectIsStaleByIndex( int i ) const
|
|
{
|
|
if( i < 0 || i >= objectStates.Num() )
|
|
{
|
|
return false;
|
|
}
|
|
return objectStates[i]->stale;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::ObjectChangedCountByIndex
|
|
========================
|
|
*/
|
|
int idSnapShot::ObjectChangedCountByIndex( int i ) const
|
|
{
|
|
if( i < 0 || i >= objectStates.Num() )
|
|
{
|
|
return false;
|
|
}
|
|
return objectStates[i]->changedCount;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::FindObjectIndexByID
|
|
========================
|
|
*/
|
|
int idSnapShot::FindObjectIndexByID( int objectNum ) const
|
|
{
|
|
int i = BinarySearch( objectNum );
|
|
if( i >= 0 && i < objectStates.Num() && objectStates[i]->objectNum == objectNum )
|
|
{
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::BinarySearch
|
|
========================
|
|
*/
|
|
int idSnapShot::BinarySearch( int objectNum ) const
|
|
{
|
|
int lo = 0;
|
|
int hi = objectStates.Num();
|
|
while( hi != lo )
|
|
{
|
|
int mid = ( hi + lo ) >> 1;
|
|
|
|
if( objectStates[mid]->objectNum == objectNum )
|
|
{
|
|
return mid; // Early out if we can
|
|
}
|
|
|
|
if( objectStates[mid]->objectNum < objectNum )
|
|
{
|
|
lo = mid + 1;
|
|
}
|
|
else
|
|
{
|
|
hi = mid;
|
|
}
|
|
}
|
|
return hi;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::FindOrCreateObjectByID
|
|
========================
|
|
*/
|
|
idSnapShot::objectState_t& idSnapShot::FindOrCreateObjectByID( int objectNum )
|
|
{
|
|
//assert( mem.IsMapHeap() );
|
|
|
|
int i = BinarySearch( objectNum );
|
|
|
|
if( i >= 0 && i < objectStates.Num() && objectStates[i]->objectNum == objectNum )
|
|
{
|
|
return *objectStates[i];
|
|
}
|
|
|
|
objectState_t* newstate = allocatedObjs.Alloc();
|
|
newstate->objectNum = objectNum;
|
|
|
|
objectStates.Insert( newstate, i );
|
|
|
|
return *objectStates[i];
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::FindObjectByID
|
|
========================
|
|
*/
|
|
idSnapShot::objectState_t* idSnapShot::FindObjectByID( int objectNum ) const
|
|
{
|
|
|
|
//assert( mem.IsMapHeap() );
|
|
|
|
int i = BinarySearch( objectNum );
|
|
|
|
if( i >= 0 && i < objectStates.Num() && objectStates[i]->objectNum == objectNum )
|
|
{
|
|
return objectStates[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::CleanupEmptyStates
|
|
========================
|
|
*/
|
|
void idSnapShot::CleanupEmptyStates()
|
|
{
|
|
for( int i = objectStates.Num() - 1; i >= 0 ; i-- )
|
|
{
|
|
if( objectStates[i]->buffer.Size() == 0 )
|
|
{
|
|
FreeObjectState( i );
|
|
objectStates.RemoveIndex( i );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::UpdateExpectedSeq
|
|
========================
|
|
*/
|
|
void idSnapShot::UpdateExpectedSeq( int newSeq )
|
|
{
|
|
for( int i = 0; i < objectStates.Num(); i++ )
|
|
{
|
|
if( objectStates[i]->expectedSequence == -2 )
|
|
{
|
|
objectStates[i]->expectedSequence = newSeq;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::FreeObjectState
|
|
========================
|
|
*/
|
|
void idSnapShot::FreeObjectState( int index )
|
|
{
|
|
assert( objectStates[index] != NULL );
|
|
//assert( mem.IsMapHeap() );
|
|
objectStates[index]->buffer._Release();
|
|
allocatedObjs.Free( objectStates[index] );
|
|
objectStates[index] = NULL;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSnapShot::ApplyToExistingState
|
|
Take uncompressed state in msg and add it to existing state
|
|
========================
|
|
*/
|
|
void idSnapShot::ApplyToExistingState( int objId, idBitMsg& msg )
|
|
{
|
|
objectState_t* objectState = FindObjectByID( objId );
|
|
if( !verify( objectState != NULL ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( !objectState->createdFromTemplate )
|
|
{
|
|
// We were created this from a template, so we shouldn't be applying it again
|
|
if( net_ssTemplateDebug.GetBool() )
|
|
{
|
|
idLib::Printf( "NOT ApplyToExistingState[%d] because object was created from existing base state. %d\n", objId, objectState->expectedSequence );
|
|
objectState->Print( "SS STATE" );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Debug print the template (spawn) and delta state
|
|
if( net_ssTemplateDebug.GetBool() )
|
|
{
|
|
idLib::Printf( "\nApplyToExistingState[%d]. buffer size: %d msg size: %d\n", objId, objectState->buffer.Size(), msg.GetSize() );
|
|
objectState->Print( "DELTA STATE" );
|
|
|
|
PrintAlign( "SPAWN STATE" );
|
|
for( int i = 0; i < msg.GetSize(); i++ )
|
|
{
|
|
if( InDebugRange( i ) )
|
|
{
|
|
idLib::Printf( "%02X", msg.GetReadData()[i] );
|
|
}
|
|
}
|
|
idLib::Printf( "\n" );
|
|
}
|
|
|
|
// Actually apply it
|
|
for( objectSize_t i = 0; i < Min( objectState->buffer.Size(), msg.GetSize() ); i++ )
|
|
{
|
|
objectState->buffer[i] += msg.GetReadData()[i];
|
|
}
|
|
|
|
// Debug print the final state
|
|
if( net_ssTemplateDebug.GetBool() )
|
|
{
|
|
objectState->Print( "NEW STATE" );
|
|
idLib::Printf( "\n" );
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
CONSOLE_COMMAND( serializeQTest, "Serialization Sanity Test", 0 )
|
|
{
|
|
|
|
byte buffer[1024];
|
|
memset( buffer, 0, sizeof( buffer ) );
|
|
|
|
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,
|
|
1.0f, 1.01f, 1.1f, 10.0f, 10.1f, 10.101f, 100.0f, 101.0f, 101.1f, 101.101f
|
|
};
|
|
int num = sizeof( values ) / sizeof( float );
|
|
|
|
idLib::Printf( "\n^3Testing SerializeQ and SerializeUQ \n" );
|
|
|
|
{
|
|
idBitMsg writeBitMsg;
|
|
writeBitMsg.InitWrite( buffer, sizeof( buffer ) );
|
|
idSerializer writeSerializer( writeBitMsg, true );
|
|
|
|
for( int i = 0; i < num; i++ )
|
|
{
|
|
writeSerializer.SerializeUQ( values[i], 255.0f, 16 );
|
|
writeSerializer.SerializeQ( values[i], 128.0f, 16 );
|
|
}
|
|
}
|
|
|
|
{
|
|
idBitMsg readBitMsg;
|
|
readBitMsg.InitRead( buffer, sizeof( buffer ) );
|
|
idSerializer readSerializer( readBitMsg, false );
|
|
|
|
for( int i = 0; i < num; i++ )
|
|
{
|
|
|
|
float resultUQ = -999.0f;
|
|
float resultQ = -999.0f;
|
|
|
|
readSerializer.SerializeUQ( resultUQ, 255.0f, 16 );
|
|
readSerializer.SerializeQ( resultQ, 128.0f, 16 );
|
|
|
|
float errorUQ = idMath::Fabs( ( resultUQ - values[i] ) ) / values[i];
|
|
float errorQ = idMath::Fabs( ( resultQ - values[i] ) ) / values[i];
|
|
|
|
idLib::Printf( "%s%f SerializeUQ: %f. Error: %f \n", errorUQ > 0.1f ? "^1" : "", values[i], resultUQ, errorUQ );
|
|
idLib::Printf( "%s%f SerializeQ: %f. Error: %f \n", errorQ > 0.1f ? "^1" : "", values[i], resultQ, errorQ );
|
|
}
|
|
}
|
|
}
|
|
#endif
|