403 lines
12 KiB
C++
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
|
|
|