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.
372 lines
9.3 KiB
372 lines
9.3 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $Workfile: $ |
|
// $Date: $ |
|
// |
|
//----------------------------------------------------------------------------- |
|
// $Log: $ |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
#include "quakedef.h" |
|
#include <stddef.h> |
|
#include "vengineserver_impl.h" |
|
#include "server.h" |
|
#include "pr_edict.h" |
|
#include "world.h" |
|
#include "ispatialpartition.h" |
|
#include "utllinkedlist.h" |
|
#include "framesnapshot.h" |
|
#include "sv_log.h" |
|
#include "tier1/utlmap.h" |
|
#include "tier1/utlvector.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
// Edicts won't get reallocated for this many seconds after being freed. |
|
#define EDICT_FREETIME 1.0 |
|
|
|
|
|
|
|
#ifdef _DEBUG |
|
#ifdef OSX |
|
#include <malloc/malloc.h> |
|
#else |
|
#include <malloc.h> |
|
#endif |
|
#endif // _DEBUG |
|
|
|
static ConVar sv_useexplicitdelete( "sv_useexplicitdelete", "1", FCVAR_DEVELOPMENTONLY, "Explicitly delete dormant client entities caused by AllowImmediateReuse()." ); |
|
static ConVar sv_lowedict_threshold( "sv_lowedict_threshold", "8", FCVAR_NONE, "When only this many edicts are free, take the action specified by sv_lowedict_action.", true, 0, true, MAX_EDICTS ); |
|
static ConVar sv_lowedict_action( "sv_lowedict_action", "0", FCVAR_NONE, "0 - no action, 1 - warn to log file, 2 - attempt to restart the game, if applicable, 3 - restart the map, 4 - go to the next map in the map cycle, 5 - spew all edicts.", true, 0, true, 5 ); |
|
|
|
// Bitmask of free edicts. |
|
static CBitVec< MAX_EDICTS > g_FreeEdicts; |
|
|
|
/* |
|
================= |
|
ED_ClearEdict |
|
|
|
Sets everything to NULL, done when new entity is allocated for game.dll |
|
================= |
|
*/ |
|
static void ED_ClearEdict( edict_t *e ) |
|
{ |
|
e->ClearFree(); |
|
e->ClearStateChanged(); |
|
e->SetChangeInfoSerialNumber( 0 ); |
|
|
|
serverGameEnts->FreeContainingEntity(e); |
|
InitializeEntityDLLFields(e); |
|
|
|
e->m_NetworkSerialNumber = -1; // must be filled by game.dll |
|
Assert( (int) e->m_EdictIndex == (e - sv.edicts) ); |
|
} |
|
|
|
void ED_ClearFreeFlag( edict_t *e ) |
|
{ |
|
e->ClearFree(); |
|
g_FreeEdicts.Clear( e->m_EdictIndex ); |
|
} |
|
|
|
void ED_ClearFreeEdictList() |
|
{ |
|
// Clear the free edicts bitfield. |
|
g_FreeEdicts.ClearAll(); |
|
} |
|
|
|
static void SpewEdicts() |
|
{ |
|
CUtlMap< const char*, int > mapEnts; |
|
mapEnts.SetLessFunc( StringLessThan ); |
|
|
|
// Tally up each class |
|
int nEdictNum = 1; |
|
for( int i=0; i<sv.num_edicts; ++i ) |
|
{ |
|
edict_t *e = &sv.edicts[i]; |
|
++nEdictNum; |
|
unsigned short nIndex = mapEnts.Find( e->GetClassName() ); |
|
if ( nIndex == mapEnts.InvalidIndex() ) |
|
{ |
|
nIndex = mapEnts.Insert( e->GetClassName() ); |
|
mapEnts[ nIndex ] = 0; |
|
} |
|
mapEnts[ nIndex ] = mapEnts[ nIndex ] + 1; |
|
} |
|
|
|
struct EdictCount_t |
|
{ |
|
EdictCount_t( const char *pszClassName, int nCount ) |
|
: |
|
m_pszClassName( pszClassName ), |
|
m_nCount( nCount ) |
|
{} |
|
|
|
const char *m_pszClassName; |
|
int m_nCount; |
|
}; |
|
|
|
CUtlVector<EdictCount_t*> vecEnts; |
|
|
|
FOR_EACH_MAP_FAST( mapEnts, i ) |
|
{ |
|
vecEnts.AddToTail( new EdictCount_t( mapEnts.Key( i ), mapEnts[ i ] ) ); |
|
} |
|
|
|
struct EdictSorter_t |
|
{ |
|
static int SortEdicts( EdictCount_t* const *p1, EdictCount_t* const *p2 ) |
|
{ |
|
return (*p1)->m_nCount - (*p2)->m_nCount; |
|
} |
|
}; |
|
|
|
vecEnts.Sort( &EdictSorter_t::SortEdicts ); |
|
|
|
g_Log.Printf( "Spewing edict counts:\n" ); |
|
FOR_EACH_VEC( vecEnts, i ) |
|
{ |
|
g_Log.Printf( "(%3.2f%%) %d\t%s\n", vecEnts[i]->m_nCount/(float)nEdictNum * 100.f, vecEnts[i]->m_nCount, vecEnts[i]->m_pszClassName ); |
|
} |
|
g_Log.Printf( "Total edicts: %d\n", nEdictNum ); |
|
|
|
vecEnts.PurgeAndDeleteElements(); |
|
} |
|
|
|
/* |
|
================= |
|
ED_Alloc |
|
|
|
Either finds a free edict, or allocates a new one. |
|
Try to avoid reusing an entity that was recently freed, because it |
|
can cause the client to think the entity morphed into something else |
|
instead of being removed and recreated, which can cause interpolated |
|
angles and bad trails. |
|
================= |
|
*/ |
|
|
|
|
|
edict_t *ED_Alloc( int iForceEdictIndex ) |
|
{ |
|
if ( iForceEdictIndex >= 0 ) |
|
{ |
|
if ( iForceEdictIndex >= sv.num_edicts ) |
|
{ |
|
Warning( "ED_Alloc( %d ) - invalid edict index specified.", iForceEdictIndex ); |
|
return NULL; |
|
} |
|
|
|
edict_t *e = &sv.edicts[ iForceEdictIndex ]; |
|
if ( e->IsFree() ) |
|
{ |
|
Assert( iForceEdictIndex == e->m_EdictIndex ); |
|
--sv.free_edicts; |
|
Assert( g_FreeEdicts.IsBitSet( iForceEdictIndex ) ); |
|
g_FreeEdicts.Clear( iForceEdictIndex ); |
|
ED_ClearEdict( e ); |
|
return e; |
|
} |
|
else |
|
{ |
|
return NULL; |
|
} |
|
} |
|
|
|
// Check the free list first. |
|
int iBit = -1; |
|
for ( ;; ) |
|
{ |
|
iBit = g_FreeEdicts.FindNextSetBit( iBit + 1 ); |
|
if ( iBit < 0 ) |
|
break; |
|
|
|
edict_t *pEdict = &sv.edicts[ iBit ]; |
|
|
|
// If this assert goes off, someone most likely called pedict->ClearFree() and not ED_ClearFreeFlag()? |
|
Assert( pEdict->IsFree() ); |
|
Assert( iBit == pEdict->m_EdictIndex ); |
|
if ( ( pEdict->freetime < 2 ) || ( sv.GetTime() - pEdict->freetime >= EDICT_FREETIME ) ) |
|
{ |
|
// If we have no freetime, we've had AllowImmediateReuse() called. We need |
|
// to explicitly delete this old entity. |
|
if ( pEdict->freetime == 0 && sv_useexplicitdelete.GetBool() ) |
|
{ |
|
//Warning("ADDING SLOT to snapshot: %d\n", i ); |
|
framesnapshotmanager->AddExplicitDelete( iBit ); |
|
} |
|
|
|
--sv.free_edicts; |
|
g_FreeEdicts.Clear( pEdict->m_EdictIndex ); |
|
ED_ClearEdict( pEdict ); |
|
return pEdict; |
|
} |
|
} |
|
|
|
// Allocate a new edict. |
|
if ( sv.num_edicts >= sv.max_edicts ) |
|
{ |
|
AssertMsg( 0, "Can't allocate edict" ); |
|
|
|
SpewEdicts(); // Log the entities we have before we die |
|
|
|
if ( sv.max_edicts == 0 ) |
|
Sys_Error( "ED_Alloc: No edicts yet" ); |
|
Sys_Error ("ED_Alloc: no free edicts"); |
|
} |
|
|
|
// Do this before clearing since clear now needs to call back into the edict to deduce the index so can get the changeinfo data in the parallel structure |
|
edict_t *pEdict = sv.edicts + sv.num_edicts++; |
|
|
|
// We should not be in the free list... |
|
Assert( !g_FreeEdicts.IsBitSet( pEdict->m_EdictIndex ) ); |
|
ED_ClearEdict( pEdict ); |
|
|
|
if ( sv_lowedict_action.GetInt() > 0 && sv.num_edicts >= sv.max_edicts - sv_lowedict_threshold.GetInt() ) |
|
{ |
|
int nEdictsRemaining = sv.max_edicts - sv.num_edicts; |
|
g_Log.Printf( "Warning: free edicts below threshold. %i free edict%s remaining.\n", nEdictsRemaining, nEdictsRemaining == 1 ? "" : "s" ); |
|
|
|
switch ( sv_lowedict_action.GetInt() ) |
|
{ |
|
case 2: |
|
// restart the game |
|
{ |
|
ConVarRef mp_restartgame_immediate( "mp_restartgame_immediate" ); |
|
if ( mp_restartgame_immediate.IsValid() ) |
|
{ |
|
mp_restartgame_immediate.SetValue( 1 ); |
|
} |
|
else |
|
{ |
|
ConVarRef mp_restartgame( "mp_restartgame" ); |
|
if ( mp_restartgame.IsValid() ) |
|
{ |
|
mp_restartgame.SetValue( 1 ); |
|
} |
|
} |
|
} |
|
break; |
|
case 3: |
|
// restart the map |
|
g_pVEngineServer->ChangeLevel( sv.GetMapName(), NULL ); |
|
break; |
|
case 4: |
|
// go to the next map |
|
g_pVEngineServer->ServerCommand( "changelevel_next\n" ); |
|
break; |
|
case 5: |
|
// spew all edicts |
|
SpewEdicts(); |
|
break; |
|
} |
|
} |
|
|
|
return pEdict; |
|
} |
|
|
|
|
|
void ED_AllowImmediateReuse() |
|
{ |
|
edict_t *pEdict = sv.edicts + sv.GetMaxClients() + 1; |
|
for ( int i=sv.GetMaxClients()+1; i < sv.num_edicts; i++ ) |
|
{ |
|
if ( pEdict->IsFree() ) |
|
{ |
|
pEdict->freetime = 0; |
|
} |
|
|
|
pEdict++; |
|
} |
|
} |
|
|
|
|
|
/* |
|
================= |
|
ED_Free |
|
|
|
Marks the edict as free |
|
FIXME: walk all entities and NULL out references to this entity |
|
================= |
|
*/ |
|
void ED_Free (edict_t *ed) |
|
{ |
|
if (ed->IsFree()) |
|
{ |
|
#ifdef _DEBUG |
|
// ConDMsg("duplicate free on '%s'\n", pr_strings + ed->classname ); |
|
#endif |
|
return; |
|
} |
|
|
|
// don't free player edicts |
|
if ( (ed - sv.edicts) >= 1 && (ed - sv.edicts) <= sv.GetMaxClients() ) |
|
return; |
|
|
|
// release the DLL entity that's attached to this edict, if any |
|
serverGameEnts->FreeContainingEntity( ed ); |
|
|
|
ed->SetFree(); |
|
ed->freetime = sv.GetTime(); |
|
|
|
++sv.free_edicts; |
|
Assert( !g_FreeEdicts.IsBitSet( ed->m_EdictIndex ) ); |
|
g_FreeEdicts.Set( ed->m_EdictIndex ); |
|
|
|
// Increment the serial number so it knows to send explicit deletes the clients. |
|
ed->m_NetworkSerialNumber++; |
|
} |
|
|
|
// |
|
// serverGameEnts->FreeContainingEntity( pEdict ) frees up memory associated with a DLL entity. |
|
// InitializeEntityDLLFields clears out fields to NULL or UNKNOWN. |
|
// Release is for terminating a DLL entity. Initialize is for initializing one. |
|
// |
|
void InitializeEntityDLLFields( edict_t *pEdict ) |
|
{ |
|
// clear all the game variables |
|
size_t sz = offsetof( edict_t, m_pUnk ) + sizeof( void* ); |
|
memset( ((byte*)pEdict) + sz, 0, sizeof(edict_t) - sz ); |
|
} |
|
|
|
int NUM_FOR_EDICT(const edict_t *e) |
|
{ |
|
if ( &sv.edicts[e->m_EdictIndex] == e ) // NOTE: old server.dll may stomp m_EdictIndex |
|
return e->m_EdictIndex; |
|
int index = e - sv.edicts; |
|
if ( (unsigned int) index >= (unsigned int) sv.num_edicts ) |
|
Sys_Error ("NUM_FOR_EDICT: bad pointer"); |
|
return index; |
|
} |
|
|
|
edict_t *EDICT_NUM(int n) |
|
{ |
|
if ((unsigned int) n >= (unsigned int) sv.max_edicts) |
|
Sys_Error ("EDICT_NUM: bad number %i", n); |
|
return &sv.edicts[n]; |
|
} |
|
|
|
|
|
static inline int NUM_FOR_EDICTINFO(const edict_t *e) |
|
{ |
|
if ( &sv.edicts[e->m_EdictIndex] == e ) // NOTE: old server.dll may stomp m_EdictIndex |
|
return e->m_EdictIndex; |
|
int index = e - sv.edicts; |
|
if ( (unsigned int) index >= (unsigned int) sv.max_edicts ) |
|
Sys_Error ("NUM_FOR_EDICTINFO: bad pointer"); |
|
return index; |
|
} |
|
|
|
|
|
IChangeInfoAccessor *CBaseEdict::GetChangeAccessor() |
|
{ |
|
return &sv.edictchangeinfo[ NUM_FOR_EDICTINFO( (const edict_t*)this ) ]; |
|
} |
|
|
|
const IChangeInfoAccessor *CBaseEdict::GetChangeAccessor() const |
|
{ |
|
return &sv.edictchangeinfo[ NUM_FOR_EDICTINFO( (const edict_t*)this ) ]; |
|
}
|
|
|