csgo-2018-source/engine/serializedentity.cpp
2021-07-24 21:11:47 -07:00

403 lines
12 KiB
C++

//===================== Copyright (c) Valve Corporation. All Rights Reserved. ======================
//
//
//
//==================================================================================================
#include "tier1/bitbuf.h"
#include "serializedentity.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
DEFINE_FIXEDSIZE_ALLOCATOR_MT( CSerializedEntity, 2048, CUtlMemoryPool::GROW_FAST );
//-----------------------------------------------------------------------------
CSerializedEntity::CSerializedEntity()
: m_pFields( NULL )
, m_pFieldDataOffsets( NULL )
, m_nFieldCount( 0 )
, m_nNumReservedFields( 0 )
, m_nFieldDataBits( 0 )
, m_pFieldData( NULL )
{
}
CSerializedEntity::~CSerializedEntity()
{
Clear();
}
void CSerializedEntity::SetupPackMemory( int nNumFields, int nDataBits )
{
AssertMsg( !IsPacked(), "Attempted to pack the memory inside of a Serialized entity that was already allocated. This is a memory leak." );
//determine the total size for the buffer
int fieldSize = PAD_NUMBER( nNumFields * sizeof( *m_pFields ), 4 );
int dataOffsetsSize = PAD_NUMBER( nNumFields * sizeof( *m_pFieldDataOffsets ), 4 );
int fieldDataSize = PAD_NUMBER( Bits2Bytes( nDataBits ), 4 );
int size = fieldSize + dataOffsetsSize + fieldDataSize;
if ( size )
{
//allocate a block of memory for it
uint8* pMemBlock = (uint8*) g_pMemAlloc->Alloc( size );
//and setup our pointers
m_pFields = ( short * ) pMemBlock;
m_pFieldDataOffsets = ( uint32 * ) ( pMemBlock + fieldSize );
m_pFieldData = ( uint8 * )( pMemBlock + fieldSize + dataOffsetsSize );
}
else
{
// NOTE: We can't rely on the Source 1 XBox allocator returning NULL for a zero byte allocation
m_pFields = NULL;
m_pFieldDataOffsets = NULL;
m_pFieldData = NULL;
}
m_nFieldCount = nNumFields;
m_nNumReservedFields = knPacked;
m_nFieldDataBits = nDataBits;
}
void CSerializedEntity::Pack( short *pFields, uint32 *pFieldDataOffsets, int fieldCount, uint32 nFieldDataBits, uint8 *pFieldData )
{
//setup the memory appropriately
SetupPackMemory( fieldCount, nFieldDataBits );
memcpy( m_pFields, pFields, m_nFieldCount * sizeof( *m_pFields ) );
memcpy( m_pFieldDataOffsets, pFieldDataOffsets, m_nFieldCount * sizeof( *m_pFieldDataOffsets ) );
memcpy( m_pFieldData, pFieldData, Bits2Bytes( nFieldDataBits ) );
}
void CSerializedEntity::Copy( const CSerializedEntity &other )
{
if( this != &other )
{
Clear();
Pack( other.m_pFields, other.m_pFieldDataOffsets, other.m_nFieldCount, other.m_nFieldDataBits, other.m_pFieldData );
}
}
void CSerializedEntity::Swap( CSerializedEntity& other )
{
//just swap all the pointers between the two fields
V_swap(m_pFields, other.m_pFields);
V_swap(m_pFieldDataOffsets, other.m_pFieldDataOffsets);
V_swap(m_nFieldDataBits, other.m_nFieldDataBits);
V_swap(m_pFieldData, other.m_pFieldData);
V_swap(m_nFieldCount, other.m_nFieldCount);
V_swap(m_nNumReservedFields, other.m_nNumReservedFields);
#ifdef PARANOID_SERIALIZEDENTITY
V_swap(m_File, other.m_File);
V_swap(m_Line, other.m_Line);
#endif
}
void CSerializedEntity::Clear()
{
if ( IsPacked() )
{
g_pMemAlloc->Free( m_pFields );
//the other two were allocated contiguously after the fields
}
else
{
//fields and data offsets are always allocated in a block
g_pMemAlloc->Free( m_pFields );
delete[] m_pFieldData;
}
m_nFieldDataBits = 0;
m_pFields = NULL;
m_pFieldDataOffsets = NULL;
m_pFieldData = NULL;
m_nFieldCount = 0;
m_nNumReservedFields = 0;
}
void CSerializedEntity::ReservePathAndOffsetMemory( uint32 nNumElements )
{
Assert( !IsPacked() );
//don't allow them to reserve less memory than what we have already allocated
m_nNumReservedFields = MAX( nNumElements, m_nFieldCount );
//save our old pointers so we can copy from them
short* pOldFields = m_pFields;
uint32* pOldOffsets = m_pFieldDataOffsets;
//allocate the new block
uint32 nFieldSize = PAD_NUMBER( sizeof( *m_pFields ) * m_nNumReservedFields, 4 );
uint32 nOffsetSize = sizeof( uint32 ) * m_nNumReservedFields;
int nSize = nFieldSize + nOffsetSize;
if ( nSize )
{
uint8* pMem = ( uint8* )g_pMemAlloc->Alloc( nSize );
//update our pointers
m_pFields = ( short* )pMem;
m_pFieldDataOffsets = ( uint32* )( pMem + nFieldSize);
//copy over the old data
if( pOldFields )
{
memcpy( m_pFields, pOldFields, sizeof( short ) * m_nFieldCount );
memcpy( m_pFieldDataOffsets, pOldOffsets, sizeof( uint32 ) * m_nFieldCount );
}
}
else
{
// NOTE: We can't rely on the Source 1 XBox allocator returning NULL for a zero byte allocation
m_pFields = NULL;
m_pFieldDataOffsets = NULL;
}
//cleanup the old (which is over allocated)
g_pMemAlloc->Free( pOldFields );
}
void CSerializedEntity::Grow()
{
Assert( !IsPacked() );
//determine our updated size (start at 2, grow in multiples of 2)
if ( !m_pFields )
ReservePathAndOffsetMemory( 2 );
else
ReservePathAndOffsetMemory( m_nNumReservedFields * 2 );
}
// pvecFieldPathBits is used by DTI mode
bool CSerializedEntity::ReadFieldPaths( bf_read *pBuf, CUtlVector< int > *pvecFieldPathBits /*= NULL*/ )
{
Assert( !IsPacked() );
CDeltaBitsReader reader( pBuf );
int iProp;
while ( -1 != (iProp = reader.ReadNextPropIndex()) )
{
if ( m_nFieldCount == m_nNumReservedFields )
{
Grow();
}
m_pFields[m_nFieldCount++] = iProp;
if ( pvecFieldPathBits )
{
pvecFieldPathBits->AddToTail( reader.GetFieldPathBits() );
}
}
return true;
}
//given a data chunk, this will make sure that the resulting bits in the last byte are cleared to zero to ensure that they are consistent across compares
void ClearRemainingBits( uint32 nNumBits, uint8* pBitData )
{
//determine how many bits beyond the last byte we need to keep
const uint32 nRemainingBits = nNumBits % 8;
//if the remaining bits is zero, we are byte aligned, and accessing that byte can be beyond the array
if( nRemainingBits > 0 )
{
//determine the byte to index into
const uint32 nByte = nNumBits / 8;
const uint32 nKeepMask = ( 1 << nRemainingBits ) - 1;
pBitData[ nByte ] &= nKeepMask;
}
}
void CSerializedEntity::PackWithFieldData( void *pData, int nDataBits )
{
Assert( !IsPacked() );
Assert( m_pFieldData == NULL );
short *pStartFields = m_pFields;
uint32 *pStartDataOffsets = m_pFieldDataOffsets;
//setup the memory appropriately
SetupPackMemory( m_nFieldCount, nDataBits );
memcpy( m_pFields, pStartFields, m_nFieldCount * sizeof( *m_pFields ) );
memcpy( m_pFieldDataOffsets, pStartDataOffsets, m_nFieldCount * sizeof( *m_pFieldDataOffsets ) );
memcpy( m_pFieldData, pData, Bits2Bytes( nDataBits ) );
ClearRemainingBits( nDataBits, m_pFieldData );
//free our original memory
g_pMemAlloc->Free( pStartFields );
}
void CSerializedEntity::PackWithFieldData( bf_read &buf, int nDataBits )
{
Assert( !IsPacked() );
Assert( m_pFieldData == NULL );
short *pStartFields = m_pFields;
uint32 *pStartDataOffsets = m_pFieldDataOffsets;
//setup the memory appropriately
SetupPackMemory( m_nFieldCount, nDataBits );
memcpy( m_pFields, pStartFields, m_nFieldCount * sizeof( *m_pFields ) );
memcpy( m_pFieldDataOffsets, pStartDataOffsets, m_nFieldCount * sizeof( *m_pFieldDataOffsets ) );
buf.ReadBits( m_pFieldData, nDataBits );
ClearRemainingBits( nDataBits, m_pFieldData );
//free our original memory
g_pMemAlloc->Free( pStartFields );
}
void CSerializedEntity::StartReading( bf_read& bitReader ) const
{
bitReader.StartReading( m_pFieldData, PAD_NUMBER( Bits2Bytes( m_nFieldDataBits ), 4 ), 0, m_nFieldDataBits );
}
void CSerializedEntity::StartWriting( bf_write& bitWriter )
{
bitWriter.StartWriting( m_pFieldData, PAD_NUMBER( Bits2Bytes( m_nFieldDataBits ), 4 ), 0, m_nFieldDataBits );
}
void CSerializedEntity::DumpMemInfo()
{
CUtlMemoryPool &pool = s_Allocator;
Msg("Pool: Count,Peak,Size,Total\n");
Msg(" %d,%d,%d,%d\n", pool.Count(), pool.PeakCount(), pool.BlockSize(), pool.Size() );
}
class CSerializedEntities : public ISerializedEntities
{
public:
#ifdef PARANOID_SERIALIZEDENTITY
CUtlRBTree< CSerializedEntity *, int > m_Current;
CThreadFastMutex m_Mutex;
CInterlockedInt m_Allocated;
void Report()
{
if ( m_Allocated > 0 )
{
// If you see this, uncomment the #define PARANOID above and run again to see what's causing leaks
Msg( "%d CSerializedEntity object leaked!!!\n", (int)m_Allocated );
}
}
#endif //PARANOID_SERIALIZEDENTITY
CSerializedEntities()
#ifdef PARANOID_SERIALIZEDENTITY
: m_Current( 0, 0, DefLessFunc( CSerializedEntity * ) )
, m_Allocated( 0 )
#endif
{
}
~CSerializedEntities()
{
#ifdef PARANOID_SERIALIZEDENTITY
Report();
FOR_EACH_UTLRBTREE( m_Current, i )
{
Msg( "Leak %s:%d\n", m_Current[ i ]->m_File, m_Current[ i ]->m_Line );
}
#endif
}
virtual SerializedEntityHandle_t AllocateSerializedEntity(char const *pFile, int nLine )
{
CSerializedEntity *pEntity = new CSerializedEntity();
#ifdef PARANOID_SERIALIZEDENTITY
pEntity->m_File = pFile;
pEntity->m_Line = nLine;
m_Allocated++;
{
AUTO_LOCK_FM( m_Mutex );
m_Current.Insert( pEntity );
}
#endif
return reinterpret_cast< SerializedEntityHandle_t >( pEntity );
}
virtual void ReleaseSerializedEntity( SerializedEntityHandle_t handle )
{
if ( handle == SERIALIZED_ENTITY_HANDLE_INVALID )
return;
CSerializedEntity *pEntity = reinterpret_cast< CSerializedEntity * >( handle );
#ifdef PARANOID_SERIALIZEDENTITY
m_Allocated--;
{
AUTO_LOCK_FM( m_Mutex );
m_Current.Remove( pEntity );
}
#endif
delete pEntity;
}
virtual SerializedEntityHandle_t CopySerializedEntity( SerializedEntityHandle_t handle, char const *pFile, int nLine )
{
if ( handle == SERIALIZED_ENTITY_HANDLE_INVALID )
{
Assert( 0 );
return SERIALIZED_ENTITY_HANDLE_INVALID;
}
CSerializedEntity *pSrc = reinterpret_cast< CSerializedEntity * >( handle );
Assert( pSrc );
CSerializedEntity *pDest = reinterpret_cast< CSerializedEntity * >( AllocateSerializedEntity(pFile, nLine) );
pDest->Copy( *pSrc );
return reinterpret_cast< SerializedEntityHandle_t >( pDest );
}
};
static CSerializedEntities g_SerializedEntities;
ISerializedEntities *g_pSerializedEntities = &g_SerializedEntities;
CON_COMMAND( sv_dump_serialized_entities_mem, "Dump serialized entity allocations stats." )
{
CSerializedEntity::DumpMemInfo();
#ifdef PARANOID_SERIALIZEDENTITY
// Use int64 to avoid integer overflow when we do the calculations incorrectly.
int64 totalBytes = 0;
FOR_EACH_UTLRBTREE( g_SerializedEntities.m_Current, i )
{
CSerializedEntity *pEntity = g_SerializedEntities.m_Current[ i ];
// m_nNumReservedFields indicates whether the entity is packed, which then determines how
// we retrieve the size.
int numFields = pEntity->IsPacked() ? pEntity->m_nFieldCount : pEntity->m_nNumReservedFields;
int fieldSize = PAD_NUMBER( numFields * sizeof( *pEntity->m_pFields ), 4 );
int dataOffsetsSize = PAD_NUMBER( numFields * sizeof( *pEntity->m_pFieldDataOffsets ), 4 );
int fieldDataSize = PAD_NUMBER( Bits2Bytes( pEntity->m_nFieldDataBits ), 4 );
int size = fieldSize + dataOffsetsSize + fieldDataSize;
totalBytes += size;
}
double totalKB = totalBytes / 1024.0;
unsigned int count = g_SerializedEntities.m_Current.Count();
Msg( "SerializedEntity: %u, %1.1f KB, %1.1f KB/CSerializedEntity,\n", count, totalKB, totalKB / count );
#endif
}
#ifdef PARANOID_SERIALIZEDENTITY
CON_COMMAND( sv_dump_serialized_entities, "Dump serialized entity allocations." )
{
FOR_EACH_UTLRBTREE( g_SerializedEntities.m_Current, i )
{
Msg( "SerializedEntity from %s:%d: %d fields, %d bits\n",
g_SerializedEntities.m_Current[ i ]->m_File,
g_SerializedEntities.m_Current[ i ]->m_Line,
g_SerializedEntities.m_Current[ i ]->m_nFieldCount,
g_SerializedEntities.m_Current[ i ]->m_nFieldDataBits );
}
}
#endif