mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-12-02 17:02:17 +00:00
484 lines
14 KiB
C++
484 lines
14 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 "precompiled.h"
|
|
|
|
#include "Snapshot_Jobs.h"
|
|
|
|
uint32 SnapObjChecksum( const uint8* data, int length )
|
|
{
|
|
// RB: 64 bit fixes, changed long to int
|
|
extern unsigned int CRC32_BlockChecksum( const void * data, int length );
|
|
// RB end
|
|
|
|
return CRC32_BlockChecksum( data, length );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
ObjectsSame
|
|
========================
|
|
*/
|
|
ID_INLINE bool ObjectsSame( objJobState_t& newState, objJobState_t& oldState )
|
|
{
|
|
assert( newState.valid && oldState.valid );
|
|
assert( newState.objectNum == oldState.objectNum );
|
|
|
|
if( newState.size != oldState.size )
|
|
{
|
|
//assert( newState.data != oldState.data) );
|
|
return false; // Can't match if sizes different
|
|
}
|
|
|
|
/*
|
|
if ( newState.data == oldState.data ) {
|
|
return true; // Definite match
|
|
}
|
|
*/
|
|
|
|
if( memcmp( newState.data, oldState.data, newState.size ) == 0 )
|
|
{
|
|
return true; // Byte match, same
|
|
}
|
|
|
|
return false; // Not the same
|
|
}
|
|
|
|
/*
|
|
========================
|
|
SnapshotObjectJob
|
|
This job processes objects by delta comparing them, and then zrle encoding them to the dest stream
|
|
The dest stream is then eventually read by the lzw job, and then lzw compressed into the final delta packet
|
|
ready to be sent to peers.
|
|
========================
|
|
*/
|
|
void SnapshotObjectJob( objParms_t* parms )
|
|
{
|
|
int visIndex = parms->visIndex;
|
|
objJobState_t& newState = parms->newState;
|
|
objJobState_t& oldState = parms->oldState;
|
|
objHeader_t* header = parms->destHeader;
|
|
uint8* dataStart = parms->dest;
|
|
|
|
assert( newState.valid || oldState.valid );
|
|
|
|
// Setup header
|
|
header->flags = 0;
|
|
header->size = newState.valid ? newState.size : 0;
|
|
header->csize = 0;
|
|
header->objID = -1; // Default to ack
|
|
header->data = dataStart;
|
|
|
|
assert( header->size <= MAX_UNSIGNED_TYPE( objectSize_t ) );
|
|
|
|
// Setup checksum and tag
|
|
#ifdef SNAPSHOT_CHECKSUMS
|
|
header->checksum = 0;
|
|
#endif
|
|
|
|
idZeroRunLengthCompressor rleCompressor;
|
|
|
|
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.valid && oldState.valid )
|
|
{
|
|
// 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.size == 0 ) != ( oldState.size == 0 );
|
|
|
|
if( !oldVisible && !newVisible )
|
|
{
|
|
// object is stale and ack'ed for this client, write nothing (see 'same object' below)
|
|
header->flags |= OBJ_SAME;
|
|
return;
|
|
}
|
|
else if( oldVisible && !newVisible )
|
|
{
|
|
//SNAP_VERBOSE_PRINT( "object %d to client %d goes stale\n", newState->objectNum, visIndex );
|
|
visChange = true;
|
|
visSendState = false;
|
|
}
|
|
else if( !oldVisible && newVisible )
|
|
{
|
|
//SNAP_VERBOSE_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 && ObjectsSame( newState, oldState ) )
|
|
{
|
|
// same state, write nothing
|
|
header->flags |= OBJ_SAME;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Get the id of the object we are writing out
|
|
int32 objectNum = ( newState.valid ) ? newState.objectNum : oldState.objectNum;
|
|
|
|
// Write out object id
|
|
header->objID = objectNum;
|
|
|
|
if( !newState.valid )
|
|
{
|
|
// Deleted, write 0 size
|
|
assert( oldState.valid );
|
|
header->flags |= OBJ_DELETED;
|
|
}
|
|
else if( !oldState.valid )
|
|
{
|
|
// New object, write out full state
|
|
assert( newState.valid );
|
|
// delta against an empty snap
|
|
rleCompressor.Start( dataStart, NULL, OBJ_DEST_SIZE_ALIGN16( newState.size ) );
|
|
rleCompressor.WriteBytes( newState.data, newState.size );
|
|
header->csize = rleCompressor.End();
|
|
header->flags |= OBJ_NEW;
|
|
if( header->csize == -1 )
|
|
{
|
|
// Not enough space, don't compress, have lzw job do zrle compression instead
|
|
memcpy( dataStart, newState.data, newState.size );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Compare to same obj id in different snapshot
|
|
assert( newState.objectNum == oldState.objectNum );
|
|
|
|
header->flags |= OBJ_DIFFERENT;
|
|
|
|
if( visChange )
|
|
{
|
|
header->flags |= visSendState ? OBJ_VIS_NOT_STALE : OBJ_VIS_STALE;
|
|
}
|
|
|
|
if( !visChange || visSendState )
|
|
{
|
|
int compareSize = Min( newState.size, oldState.size );
|
|
rleCompressor.Start( dataStart, NULL, OBJ_DEST_SIZE_ALIGN16( newState.size ) );
|
|
for( int b = 0; b < compareSize; b++ )
|
|
{
|
|
byte delta = newState.data[b] - oldState.data[b];
|
|
rleCompressor.WriteByte( ( 0xFF + 1 + delta ) & 0xFF );
|
|
}
|
|
// Get leftover
|
|
int leftOver = newState.size - compareSize;
|
|
|
|
if( leftOver > 0 )
|
|
{
|
|
rleCompressor.WriteBytes( newState.data + compareSize, leftOver );
|
|
}
|
|
|
|
header->csize = rleCompressor.End();
|
|
|
|
if( header->csize == -1 )
|
|
{
|
|
// Not enough space, don't compress, have lzw job do zrle compression instead
|
|
for( int b = 0; b < compareSize; b++ )
|
|
{
|
|
*dataStart++ = ( ( 0xFF + 1 + ( newState.data[b] - oldState.data[b] ) ) & 0xFF );
|
|
}
|
|
// Get leftover
|
|
int leftOver = newState.size - compareSize;
|
|
|
|
if( leftOver > 0 )
|
|
{
|
|
memcpy( dataStart, newState.data + compareSize, leftOver );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
assert( header->csize <= OBJ_DEST_SIZE_ALIGN16( header->size ) );
|
|
|
|
#ifdef SNAPSHOT_CHECKSUMS
|
|
if( newState.valid )
|
|
{
|
|
assert( newState.size );
|
|
header->checksum = SnapObjChecksum( newState.data, newState.size );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
========================
|
|
FinishLZWStream
|
|
========================
|
|
*/
|
|
static void FinishLZWStream( lzwParm_t* parm, idLZWCompressor* lzwCompressor )
|
|
{
|
|
if( lzwCompressor->IsOverflowed() )
|
|
{
|
|
lzwCompressor->Restore();
|
|
}
|
|
|
|
lzwDelta_t& pendingDelta = parm->ioData->lzwDeltas[parm->ioData->numlzwDeltas];
|
|
|
|
if( lzwCompressor->End() == -1 )
|
|
{
|
|
// If we couldn't end the stream, notify the main thread
|
|
pendingDelta.offset = -1;
|
|
pendingDelta.size = -1;
|
|
pendingDelta.snapSequence = -1;
|
|
|
|
parm->ioData->numlzwDeltas++;
|
|
return;
|
|
}
|
|
|
|
int size = lzwCompressor->Length();
|
|
|
|
pendingDelta.offset = parm->ioData->lzwBytes; // Remember offset into buffer
|
|
pendingDelta.size = size; // Remember size
|
|
pendingDelta.snapSequence = parm->ioData->snapSequence; // Remember which snap sequence this delta belongs to
|
|
|
|
parm->ioData->lzwBytes += size;
|
|
parm->ioData->numlzwDeltas++;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
NewLZWStream
|
|
========================
|
|
*/
|
|
static void NewLZWStream( lzwParm_t* parm, idLZWCompressor* lzwCompressor )
|
|
{
|
|
|
|
// Reset compressor
|
|
int maxSize = parm->ioData->maxlzwMem - parm->ioData->lzwBytes;
|
|
lzwCompressor->Start( &parm->ioData->lzwMem[parm->ioData->lzwBytes], maxSize );
|
|
|
|
parm->ioData->lastObjId = 0;
|
|
|
|
parm->ioData->snapSequence++;
|
|
|
|
lzwCompressor->WriteAgnostic( parm->ioData->snapSequence );
|
|
lzwCompressor->WriteAgnostic( parm->baseSequence );
|
|
lzwCompressor->WriteAgnostic( parm->curTime );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
ContinueLZWStream
|
|
========================
|
|
*/
|
|
static void ContinueLZWStream( lzwParm_t* parm, idLZWCompressor* lzwCompressor )
|
|
{
|
|
// Continue compressor where we left off
|
|
int maxSize = parm->ioData->maxlzwMem - parm->ioData->lzwBytes;
|
|
lzwCompressor->Start( &parm->ioData->lzwMem[parm->ioData->lzwBytes], maxSize, true );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
LZWJobInternal
|
|
This job takes a stream of objects, which should already be zrle compressed, and then lzw compresses them
|
|
and builds a final delta packet ready to be sent to peers.
|
|
========================
|
|
*/
|
|
void LZWJobInternal( lzwParm_t* parm, unsigned int dmaTag )
|
|
{
|
|
assert( parm->numObjects > 0 );
|
|
|
|
#ifndef ALLOW_MULTIPLE_DELTAS
|
|
if( parm->ioData->numlzwDeltas > 0 )
|
|
{
|
|
// Currently, we don't use fragmented deltas.
|
|
// We only send the first one and rely on a full snap being sent to get the whole snap across
|
|
assert( parm->ioData->numlzwDeltas == 1 );
|
|
assert( !parm->ioData->fullSnap );
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
assert( parm->ioData->lzwBytes < parm->ioData->maxlzwMem );
|
|
|
|
dmaTag = dmaTag;
|
|
|
|
#ifdef __GNUC__
|
|
// DG: remove ALIGN16 for GCC/clang, as they can't use it here and clang gets an error
|
|
idLZWCompressor lzwCompressor( parm->ioData->lzwData );
|
|
// DG end
|
|
#else
|
|
ALIGN16( idLZWCompressor lzwCompressor( parm->ioData->lzwData ) );
|
|
#endif
|
|
|
|
if( parm->fragmented )
|
|
{
|
|
// This packet was partially written out, we need to continue writing, using previous lzw dictionary values
|
|
ContinueLZWStream( parm, &lzwCompressor );
|
|
}
|
|
else
|
|
{
|
|
// We can start a new lzw dictionary
|
|
NewLZWStream( parm, &lzwCompressor );
|
|
}
|
|
|
|
|
|
int numChangedObjProcessed = 0;
|
|
|
|
for( int i = 0; i < parm->numObjects; i++ )
|
|
{
|
|
|
|
// This will eventually be gracefully caught in SnapshotProcessor.cpp.
|
|
// It's nice to know right when it happens though, so you can inspect the situation.
|
|
assert( !lzwCompressor.IsOverflowed() || numChangedObjProcessed > 1 );
|
|
|
|
// First, see if we need to finish the current lzw stream
|
|
if( lzwCompressor.IsOverflowed() || lzwCompressor.Length() >= parm->ioData->optimalLength )
|
|
{
|
|
FinishLZWStream( parm, &lzwCompressor );
|
|
// indicate how much needs to be DMA'ed back out
|
|
parm->ioData->lzwDmaOut = parm->ioData->lzwBytes;
|
|
#ifdef ALLOW_MULTIPLE_DELTAS
|
|
NewLZWStream( parm, &lzwCompressor );
|
|
#else
|
|
// Currently, we don't use fragmented deltas.
|
|
// We only send the first one and rely on a full snap being sent to get the whole snap across
|
|
assert( !parm->ioData->fullSnap );
|
|
assert( parm->ioData->numlzwDeltas == 1 );
|
|
return;
|
|
#endif
|
|
}
|
|
|
|
if( numChangedObjProcessed > 0 )
|
|
{
|
|
// We should be at a good spot in the stream if we've written at least one obj without overflowing, so save it
|
|
lzwCompressor.Save();
|
|
}
|
|
|
|
// Get header
|
|
objHeader_t* header = &parm->headers[i];
|
|
|
|
if( header->objID == -1 )
|
|
{
|
|
assert( header->flags & OBJ_SAME );
|
|
continue; // Don't send object (which means ack)
|
|
}
|
|
|
|
numChangedObjProcessed++;
|
|
|
|
// Write obj id as delta into stream
|
|
lzwCompressor.WriteAgnostic<uint16>( ( uint16 )( header->objID - parm->ioData->lastObjId ) );
|
|
parm->ioData->lastObjId = ( uint16 )header->objID;
|
|
|
|
// Check special stale/notstale flags
|
|
if( header->flags & ( OBJ_VIS_STALE | OBJ_VIS_NOT_STALE ) )
|
|
{
|
|
// Write stale/notstale flag
|
|
objectSize_t value = ( header->flags & OBJ_VIS_STALE ) ? SIZE_STALE : SIZE_NOT_STALE;
|
|
lzwCompressor.WriteAgnostic<objectSize_t>( value );
|
|
}
|
|
|
|
if( header->flags & OBJ_VIS_STALE )
|
|
{
|
|
continue; // Don't write out data for stale objects
|
|
}
|
|
|
|
if( header->flags & OBJ_DELETED )
|
|
{
|
|
// Object was deleted
|
|
lzwCompressor.WriteAgnostic<objectSize_t>( 0 );
|
|
continue;
|
|
}
|
|
|
|
// Write size
|
|
lzwCompressor.WriteAgnostic<objectSize_t>( ( objectSize_t )header->size );
|
|
|
|
// Get compressed data area
|
|
uint8* compressedData = header->data;
|
|
|
|
if( header->csize == -1 )
|
|
{
|
|
// Wasn't zrle compressed, zrle now while lzw'ing
|
|
idZeroRunLengthCompressor rleCompressor;
|
|
rleCompressor.Start( NULL, &lzwCompressor, 0xFFFF );
|
|
rleCompressor.WriteBytes( compressedData, header->size );
|
|
rleCompressor.End();
|
|
}
|
|
else
|
|
{
|
|
// Write out zero-rle compressed data
|
|
lzwCompressor.Write( compressedData, header->csize );
|
|
}
|
|
|
|
#ifdef SNAPSHOT_CHECKSUMS
|
|
// Write checksum
|
|
lzwCompressor.WriteAgnostic( header->checksum );
|
|
#endif
|
|
// This will eventually be gracefully caught in SnapshotProcessor.cpp.
|
|
// It's nice to know right when it happens though, so you can inspect the situation.
|
|
assert( !lzwCompressor.IsOverflowed() || numChangedObjProcessed > 1 );
|
|
}
|
|
|
|
if( !parm->saveDictionary )
|
|
{
|
|
// Write out terminator
|
|
uint16 objectDelta = 0xFFFF - parm->ioData->lastObjId;
|
|
lzwCompressor.WriteAgnostic( objectDelta );
|
|
|
|
// Last stream
|
|
FinishLZWStream( parm, &lzwCompressor );
|
|
|
|
// indicate how much needs to be DMA'ed back out
|
|
parm->ioData->lzwDmaOut = parm->ioData->lzwBytes;
|
|
|
|
parm->ioData->fullSnap = true; // We sent a full snap
|
|
}
|
|
else
|
|
{
|
|
// the compressor did some work, wrote data to lzwMem, but since we didn't call FinishLZWStream to end the compression,
|
|
// we need to figure how much needs to be DMA'ed back out
|
|
assert( parm->ioData->lzwBytes == 0 ); // I don't think we ever hit this with lzwBytes != 0, but adding it just in case
|
|
parm->ioData->lzwDmaOut = parm->ioData->lzwBytes + lzwCompressor.Length();
|
|
}
|
|
|
|
assert( parm->ioData->lzwBytes < parm->ioData->maxlzwMem );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
LZWJob
|
|
========================
|
|
*/
|
|
void LZWJob( lzwParm_t* parm )
|
|
{
|
|
LZWJobInternal( parm, 0 );
|
|
}
|