summaryrefslogtreecommitdiff
path: root/engine/pr_edict.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/pr_edict.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'engine/pr_edict.cpp')
-rw-r--r--engine/pr_edict.cpp368
1 files changed, 368 insertions, 0 deletions
diff --git a/engine/pr_edict.cpp b/engine/pr_edict.cpp
new file mode 100644
index 0000000..755adc1
--- /dev/null
+++ b/engine/pr_edict.cpp
@@ -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 ) ];
+}