You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
573 lines
16 KiB
573 lines
16 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $Workfile: $ |
|
// $NoKeywords: $ |
|
//===========================================================================// |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: a global list of all the entities in the game. All iteration through |
|
// entities is done through this object. |
|
//----------------------------------------------------------------------------- |
|
#include "cbase.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
//----------------------------------------------------------------------------- |
|
// Globals |
|
//----------------------------------------------------------------------------- |
|
|
|
// Create interface |
|
static CClientEntityList s_EntityList; |
|
CBaseEntityList *g_pEntityList = &s_EntityList; |
|
|
|
// Expose list to engine |
|
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CClientEntityList, IClientEntityList, VCLIENTENTITYLIST_INTERFACE_VERSION, s_EntityList ); |
|
|
|
// Store local pointer to interface for rest of client .dll only |
|
// (CClientEntityList instead of IClientEntityList ) |
|
CClientEntityList *cl_entitylist = &s_EntityList; |
|
|
|
|
|
bool PVSNotifierMap_LessFunc( IClientUnknown* const &a, IClientUnknown* const &b ) |
|
{ |
|
return a < b; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CClientEntityList::CClientEntityList( void ) : |
|
m_PVSNotifierMap( 0, 0, PVSNotifierMap_LessFunc ) |
|
{ |
|
m_iMaxUsedServerIndex = -1; |
|
m_iMaxServerEnts = 0; |
|
Release(); |
|
|
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CClientEntityList::~CClientEntityList( void ) |
|
{ |
|
Release(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Clears all entity lists and releases entities |
|
//----------------------------------------------------------------------------- |
|
void CClientEntityList::Release( void ) |
|
{ |
|
// Free all the entities. |
|
ClientEntityHandle_t iter = FirstHandle(); |
|
while( iter != InvalidHandle() ) |
|
{ |
|
// Try to call release on anything we can. |
|
IClientNetworkable *pNet = GetClientNetworkableFromHandle( iter ); |
|
if ( pNet ) |
|
{ |
|
pNet->Release(); |
|
} |
|
else |
|
{ |
|
// Try to call release on anything we can. |
|
IClientThinkable *pThinkable = GetClientThinkableFromHandle( iter ); |
|
if ( pThinkable ) |
|
{ |
|
pThinkable->Release(); |
|
} |
|
} |
|
RemoveEntity( iter ); |
|
|
|
iter = FirstHandle(); |
|
} |
|
|
|
m_iNumServerEnts = 0; |
|
m_iMaxServerEnts = 0; |
|
m_iNumClientNonNetworkable = 0; |
|
m_iMaxUsedServerIndex = -1; |
|
} |
|
|
|
IClientNetworkable* CClientEntityList::GetClientNetworkable( int entnum ) |
|
{ |
|
Assert( entnum >= 0 ); |
|
Assert( entnum < MAX_EDICTS ); |
|
return m_EntityCacheInfo[entnum].m_pNetworkable; |
|
} |
|
|
|
|
|
IClientEntity* CClientEntityList::GetClientEntity( int entnum ) |
|
{ |
|
IClientUnknown *pEnt = GetListedEntity( entnum ); |
|
return pEnt ? pEnt->GetIClientEntity() : 0; |
|
} |
|
|
|
|
|
int CClientEntityList::NumberOfEntities( bool bIncludeNonNetworkable ) |
|
{ |
|
if ( bIncludeNonNetworkable == true ) |
|
return m_iNumServerEnts + m_iNumClientNonNetworkable; |
|
|
|
return m_iNumServerEnts; |
|
} |
|
|
|
|
|
void CClientEntityList::SetMaxEntities( int maxents ) |
|
{ |
|
m_iMaxServerEnts = maxents; |
|
} |
|
|
|
|
|
int CClientEntityList::GetMaxEntities( void ) |
|
{ |
|
return m_iMaxServerEnts; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Convenience methods to convert between entindex + ClientEntityHandle_t |
|
//----------------------------------------------------------------------------- |
|
int CClientEntityList::HandleToEntIndex( ClientEntityHandle_t handle ) |
|
{ |
|
if ( handle == INVALID_EHANDLE_INDEX ) |
|
return -1; |
|
C_BaseEntity *pEnt = GetBaseEntityFromHandle( handle ); |
|
return pEnt ? pEnt->entindex() : -1; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Because m_iNumServerEnts != last index |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CClientEntityList::GetHighestEntityIndex( void ) |
|
{ |
|
return m_iMaxUsedServerIndex; |
|
} |
|
|
|
void CClientEntityList::RecomputeHighestEntityUsed( void ) |
|
{ |
|
m_iMaxUsedServerIndex = -1; |
|
|
|
// Walk backward looking for first valid index |
|
int i; |
|
for ( i = MAX_EDICTS - 1; i >= 0; i-- ) |
|
{ |
|
if ( GetListedEntity( i ) != NULL ) |
|
{ |
|
m_iMaxUsedServerIndex = i; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Add a raw C_BaseEntity to the entity list. |
|
// Input : index - |
|
//----------------------------------------------------------------------------- |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : index - |
|
//----------------------------------------------------------------------------- |
|
|
|
C_BaseEntity* CClientEntityList::GetBaseEntity( int entnum ) |
|
{ |
|
IClientUnknown *pEnt = GetListedEntity( entnum ); |
|
return pEnt ? pEnt->GetBaseEntity() : 0; |
|
} |
|
|
|
|
|
ICollideable* CClientEntityList::GetCollideable( int entnum ) |
|
{ |
|
IClientUnknown *pEnt = GetListedEntity( entnum ); |
|
return pEnt ? pEnt->GetCollideable() : 0; |
|
} |
|
|
|
|
|
IClientNetworkable* CClientEntityList::GetClientNetworkableFromHandle( ClientEntityHandle_t hEnt ) |
|
{ |
|
IClientUnknown *pEnt = GetClientUnknownFromHandle( hEnt ); |
|
return pEnt ? pEnt->GetClientNetworkable() : 0; |
|
} |
|
|
|
|
|
IClientEntity* CClientEntityList::GetClientEntityFromHandle( ClientEntityHandle_t hEnt ) |
|
{ |
|
IClientUnknown *pEnt = GetClientUnknownFromHandle( hEnt ); |
|
return pEnt ? pEnt->GetIClientEntity() : 0; |
|
} |
|
|
|
|
|
IClientRenderable* CClientEntityList::GetClientRenderableFromHandle( ClientEntityHandle_t hEnt ) |
|
{ |
|
IClientUnknown *pEnt = GetClientUnknownFromHandle( hEnt ); |
|
return pEnt ? pEnt->GetClientRenderable() : 0; |
|
} |
|
|
|
|
|
C_BaseEntity* CClientEntityList::GetBaseEntityFromHandle( ClientEntityHandle_t hEnt ) |
|
{ |
|
IClientUnknown *pEnt = GetClientUnknownFromHandle( hEnt ); |
|
return pEnt ? pEnt->GetBaseEntity() : 0; |
|
} |
|
|
|
|
|
ICollideable* CClientEntityList::GetCollideableFromHandle( ClientEntityHandle_t hEnt ) |
|
{ |
|
IClientUnknown *pEnt = GetClientUnknownFromHandle( hEnt ); |
|
return pEnt ? pEnt->GetCollideable() : 0; |
|
} |
|
|
|
|
|
IClientThinkable* CClientEntityList::GetClientThinkableFromHandle( ClientEntityHandle_t hEnt ) |
|
{ |
|
IClientUnknown *pEnt = GetClientUnknownFromHandle( hEnt ); |
|
return pEnt ? pEnt->GetClientThinkable() : 0; |
|
} |
|
|
|
|
|
void CClientEntityList::AddPVSNotifier( IClientUnknown *pUnknown ) |
|
{ |
|
IClientRenderable *pRen = pUnknown->GetClientRenderable(); |
|
if ( pRen ) |
|
{ |
|
IPVSNotify *pNotify = pRen->GetPVSNotifyInterface(); |
|
if ( pNotify ) |
|
{ |
|
unsigned short index = m_PVSNotifyInfos.AddToTail(); |
|
CPVSNotifyInfo *pInfo = &m_PVSNotifyInfos[index]; |
|
pInfo->m_pNotify = pNotify; |
|
pInfo->m_pRenderable = pRen; |
|
pInfo->m_InPVSStatus = 0; |
|
pInfo->m_PVSNotifiersLink = index; |
|
|
|
m_PVSNotifierMap.Insert( pUnknown, index ); |
|
} |
|
} |
|
} |
|
|
|
|
|
void CClientEntityList::RemovePVSNotifier( IClientUnknown *pUnknown ) |
|
{ |
|
IClientRenderable *pRenderable = pUnknown->GetClientRenderable(); |
|
if ( pRenderable ) |
|
{ |
|
IPVSNotify *pNotify = pRenderable->GetPVSNotifyInterface(); |
|
if ( pNotify ) |
|
{ |
|
unsigned short index = m_PVSNotifierMap.Find( pUnknown ); |
|
if ( !m_PVSNotifierMap.IsValidIndex( index ) ) |
|
{ |
|
Warning( "PVS notifier not in m_PVSNotifierMap\n" ); |
|
Assert( false ); |
|
return; |
|
} |
|
|
|
unsigned short indexIntoPVSNotifyInfos = m_PVSNotifierMap[index]; |
|
|
|
Assert( m_PVSNotifyInfos[indexIntoPVSNotifyInfos].m_pNotify == pNotify ); |
|
Assert( m_PVSNotifyInfos[indexIntoPVSNotifyInfos].m_pRenderable == pRenderable ); |
|
|
|
m_PVSNotifyInfos.Remove( indexIntoPVSNotifyInfos ); |
|
m_PVSNotifierMap.RemoveAt( index ); |
|
return; |
|
} |
|
} |
|
|
|
// If it didn't report itself as a notifier, let's hope it's not in the notifier list now |
|
// (which would mean that it reported itself as a notifier earlier, but not now). |
|
#ifdef _DEBUG |
|
unsigned short index = m_PVSNotifierMap.Find( pUnknown ); |
|
Assert( !m_PVSNotifierMap.IsValidIndex( index ) ); |
|
#endif |
|
} |
|
|
|
void CClientEntityList::AddListenerEntity( IClientEntityListener *pListener ) |
|
{ |
|
if ( m_entityListeners.Find( pListener ) >= 0 ) |
|
{ |
|
AssertMsg( 0, "Can't add listeners multiple times\n" ); |
|
return; |
|
} |
|
m_entityListeners.AddToTail( pListener ); |
|
} |
|
|
|
void CClientEntityList::RemoveListenerEntity( IClientEntityListener *pListener ) |
|
{ |
|
m_entityListeners.FindAndRemove( pListener ); |
|
} |
|
|
|
void CClientEntityList::OnAddEntity( IHandleEntity *pEnt, CBaseHandle handle ) |
|
{ |
|
int entnum = handle.GetEntryIndex(); |
|
EntityCacheInfo_t *pCache = &m_EntityCacheInfo[entnum]; |
|
|
|
if ( entnum >= 0 && entnum < MAX_EDICTS ) |
|
{ |
|
// Update our counters. |
|
m_iNumServerEnts++; |
|
if ( entnum > m_iMaxUsedServerIndex ) |
|
{ |
|
m_iMaxUsedServerIndex = entnum; |
|
} |
|
|
|
|
|
// Cache its networkable pointer. |
|
Assert( dynamic_cast< IClientUnknown* >( pEnt ) ); |
|
Assert( ((IClientUnknown*)pEnt)->GetClientNetworkable() ); // Server entities should all be networkable. |
|
pCache->m_pNetworkable = ((IClientUnknown*)pEnt)->GetClientNetworkable(); |
|
} |
|
|
|
IClientUnknown *pUnknown = (IClientUnknown*)pEnt; |
|
|
|
// If this thing wants PVS notifications, hook it up. |
|
AddPVSNotifier( pUnknown ); |
|
|
|
// Store it in a special list for fast iteration if it's a C_BaseEntity. |
|
C_BaseEntity *pBaseEntity = pUnknown->GetBaseEntity(); |
|
if ( pBaseEntity ) |
|
{ |
|
pCache->m_BaseEntitiesIndex = m_BaseEntities.AddToTail( pBaseEntity ); |
|
|
|
if ( pBaseEntity->ObjectCaps() & FCAP_SAVE_NON_NETWORKABLE ) |
|
{ |
|
m_iNumClientNonNetworkable++; |
|
} |
|
|
|
//DevMsg(2,"Created %s\n", pBaseEnt->GetClassname() ); |
|
for ( int i = m_entityListeners.Count()-1; i >= 0; i-- ) |
|
{ |
|
m_entityListeners[i]->OnEntityCreated( pBaseEntity ); |
|
} |
|
} |
|
else |
|
{ |
|
pCache->m_BaseEntitiesIndex = m_BaseEntities.InvalidIndex(); |
|
} |
|
|
|
|
|
} |
|
|
|
#if defined( STAGING_ONLY ) |
|
|
|
// Defined in tier1 / interface.cpp for Windows and native for POSIX platforms. |
|
extern "C" int backtrace( void **buffer, int size ); |
|
|
|
static struct |
|
{ |
|
int entnum; |
|
float time; |
|
C_BaseEntity *pBaseEntity; |
|
void *backtrace_addrs[ 16 ]; |
|
} g_RemoveEntityBacktraces[ 1024 ]; |
|
static uint32 g_RemoveEntityBacktracesIndex = 0; |
|
|
|
static void OnRemoveEntityBacktraceHook( int entnum, C_BaseEntity *pBaseEntity ) |
|
{ |
|
int index = g_RemoveEntityBacktracesIndex++; |
|
if ( g_RemoveEntityBacktracesIndex >= ARRAYSIZE( g_RemoveEntityBacktraces ) ) |
|
g_RemoveEntityBacktracesIndex = 0; |
|
|
|
g_RemoveEntityBacktraces[ index ].entnum = entnum; |
|
g_RemoveEntityBacktraces[ index ].time = gpGlobals->curtime; |
|
g_RemoveEntityBacktraces[ index ].pBaseEntity = pBaseEntity; |
|
|
|
memset( g_RemoveEntityBacktraces[ index ].backtrace_addrs, 0, sizeof( g_RemoveEntityBacktraces[ index ].backtrace_addrs ) ); |
|
backtrace( g_RemoveEntityBacktraces[ index ].backtrace_addrs, ARRAYSIZE( g_RemoveEntityBacktraces[ index ].backtrace_addrs ) ); |
|
} |
|
|
|
// Should help us track down CL_PreserveExistingEntity Host_Error() issues: |
|
// 1. Set cl_removeentity_backtrace_capture to 1. |
|
// 2. When error hits, run "cl_removeentity_backtrace_dump [entnum]". |
|
// 3. In debugger, track down what functions the spewed addresses refer to. |
|
static ConVar cl_removeentity_backtrace_capture( "cl_removeentity_backtrace_capture", "0", 0, |
|
"For debugging. Capture backtraces for CClientEntityList::OnRemoveEntity calls." ); |
|
|
|
CON_COMMAND( cl_removeentity_backtrace_dump, "Dump backtraces for client OnRemoveEntity calls." ) |
|
{ |
|
if ( !cl_removeentity_backtrace_capture.GetBool() ) |
|
{ |
|
Msg( "cl_removeentity_backtrace_dump error: cl_removeentity_backtrace_capture not enabled. Backtraces not captured.\n" ); |
|
return; |
|
} |
|
|
|
int entnum = ( args.ArgC() >= 2 ) ? atoi( args[ 1 ] ) : -1; |
|
|
|
for ( int i = 0; i < ARRAYSIZE( g_RemoveEntityBacktraces ); i++ ) |
|
{ |
|
if ( g_RemoveEntityBacktraces[ i ].time && |
|
( entnum == -1 || g_RemoveEntityBacktraces[ i ].entnum == entnum ) ) |
|
{ |
|
Msg( "%d: time:%.2f pBaseEntity:%p\n", g_RemoveEntityBacktraces[i].entnum, |
|
g_RemoveEntityBacktraces[ i ].time, g_RemoveEntityBacktraces[ i ].pBaseEntity ); |
|
for ( int j = 0; j < ARRAYSIZE( g_RemoveEntityBacktraces[ i ].backtrace_addrs ); j++ ) |
|
{ |
|
Msg( " %p\n", g_RemoveEntityBacktraces[ i ].backtrace_addrs[ j ] ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
#endif // STAGING_ONLY |
|
|
|
void CClientEntityList::OnRemoveEntity( IHandleEntity *pEnt, CBaseHandle handle ) |
|
{ |
|
int entnum = handle.GetEntryIndex(); |
|
EntityCacheInfo_t *pCache = &m_EntityCacheInfo[entnum]; |
|
|
|
if ( entnum >= 0 && entnum < MAX_EDICTS ) |
|
{ |
|
// This is a networkable ent. Clear out our cache info for it. |
|
pCache->m_pNetworkable = NULL; |
|
m_iNumServerEnts--; |
|
|
|
if ( entnum >= m_iMaxUsedServerIndex ) |
|
{ |
|
RecomputeHighestEntityUsed(); |
|
} |
|
} |
|
|
|
|
|
IClientUnknown *pUnknown = (IClientUnknown*)pEnt; |
|
|
|
// If this is a PVS notifier, remove it. |
|
RemovePVSNotifier( pUnknown ); |
|
|
|
C_BaseEntity *pBaseEntity = pUnknown->GetBaseEntity(); |
|
|
|
#if defined( STAGING_ONLY ) |
|
if ( cl_removeentity_backtrace_capture.GetBool() ) |
|
{ |
|
OnRemoveEntityBacktraceHook( entnum, pBaseEntity ); |
|
} |
|
#endif // STAGING_ONLY |
|
|
|
if ( pBaseEntity ) |
|
{ |
|
if ( pBaseEntity->ObjectCaps() & FCAP_SAVE_NON_NETWORKABLE ) |
|
{ |
|
m_iNumClientNonNetworkable--; |
|
} |
|
|
|
//DevMsg(2,"Deleted %s\n", pBaseEnt->GetClassname() ); |
|
for ( int i = m_entityListeners.Count()-1; i >= 0; i-- ) |
|
{ |
|
m_entityListeners[i]->OnEntityDeleted( pBaseEntity ); |
|
} |
|
} |
|
|
|
if ( pCache->m_BaseEntitiesIndex != m_BaseEntities.InvalidIndex() ) |
|
m_BaseEntities.Remove( pCache->m_BaseEntitiesIndex ); |
|
|
|
pCache->m_BaseEntitiesIndex = m_BaseEntities.InvalidIndex(); |
|
} |
|
|
|
|
|
// Use this to iterate over all the C_BaseEntities. |
|
C_BaseEntity* CClientEntityList::FirstBaseEntity() const |
|
{ |
|
const CEntInfo *pList = FirstEntInfo(); |
|
while ( pList ) |
|
{ |
|
if ( pList->m_pEntity ) |
|
{ |
|
IClientUnknown *pUnk = static_cast<IClientUnknown*>( pList->m_pEntity ); |
|
C_BaseEntity *pRet = pUnk->GetBaseEntity(); |
|
if ( pRet ) |
|
return pRet; |
|
} |
|
pList = pList->m_pNext; |
|
} |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
C_BaseEntity* CClientEntityList::NextBaseEntity( C_BaseEntity *pEnt ) const |
|
{ |
|
if ( pEnt == NULL ) |
|
return FirstBaseEntity(); |
|
|
|
// Run through the list until we get a C_BaseEntity. |
|
const CEntInfo *pList = GetEntInfoPtr( pEnt->GetRefEHandle() ); |
|
if ( pList ) |
|
{ |
|
pList = NextEntInfo(pList); |
|
} |
|
|
|
while ( pList ) |
|
{ |
|
if ( pList->m_pEntity ) |
|
{ |
|
IClientUnknown *pUnk = static_cast<IClientUnknown*>( pList->m_pEntity ); |
|
C_BaseEntity *pRet = pUnk->GetBaseEntity(); |
|
if ( pRet ) |
|
return pRet; |
|
} |
|
pList = pList->m_pNext; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------------- // |
|
// C_AllBaseEntityIterator |
|
// -------------------------------------------------------------------------------------------------- // |
|
C_AllBaseEntityIterator::C_AllBaseEntityIterator() |
|
{ |
|
Restart(); |
|
} |
|
|
|
|
|
void C_AllBaseEntityIterator::Restart() |
|
{ |
|
m_CurBaseEntity = ClientEntityList().m_BaseEntities.Head(); |
|
} |
|
|
|
|
|
C_BaseEntity* C_AllBaseEntityIterator::Next() |
|
{ |
|
if ( m_CurBaseEntity == ClientEntityList().m_BaseEntities.InvalidIndex() ) |
|
return NULL; |
|
|
|
C_BaseEntity *pRet = ClientEntityList().m_BaseEntities[m_CurBaseEntity]; |
|
m_CurBaseEntity = ClientEntityList().m_BaseEntities.Next( m_CurBaseEntity ); |
|
return pRet; |
|
} |
|
|
|
|
|
// -------------------------------------------------------------------------------------------------- // |
|
// C_BaseEntityIterator |
|
// -------------------------------------------------------------------------------------------------- // |
|
C_BaseEntityIterator::C_BaseEntityIterator() |
|
{ |
|
Restart(); |
|
} |
|
|
|
void C_BaseEntityIterator::Restart() |
|
{ |
|
m_CurBaseEntity = ClientEntityList().m_BaseEntities.Head(); |
|
} |
|
|
|
C_BaseEntity* C_BaseEntityIterator::Next() |
|
{ |
|
// Skip dormant entities |
|
while ( m_CurBaseEntity != ClientEntityList().m_BaseEntities.InvalidIndex() ) |
|
{ |
|
C_BaseEntity *pRet = ClientEntityList().m_BaseEntities[m_CurBaseEntity]; |
|
m_CurBaseEntity = ClientEntityList().m_BaseEntities.Next( m_CurBaseEntity ); |
|
|
|
if (!pRet->IsDormant()) |
|
return pRet; |
|
} |
|
|
|
return NULL; |
|
}
|
|
|