2020-04-22 16:56:21 +00:00
//========= 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
2022-02-23 11:56:29 +00:00
# ifdef OSX
# include <malloc/malloc.h>
# else
2020-04-22 16:56:21 +00:00
# include <malloc.h>
2022-02-23 11:56:29 +00:00
# endif
2020-04-22 16:56:21 +00:00
# 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 ) ] ;
}