mirror of
https://github.com/nillerusr/source-engine.git
synced 2026-08-09 10:19:38 +00:00
1
This commit is contained in:
@@ -0,0 +1,368 @@
|
||||
//========= 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
|
||||
#include <malloc.h>
|
||||
#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 ) ];
|
||||
}
|
||||
Reference in New Issue
Block a user