//========= Copyright <EFBFBD> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
# include "cbase.h"
# include "tier0/vprof.h"
// memdbgon must be the last include file in a .cpp file!!!
# include "tier0/memdbgon.h"
CClientThinkList g_ClientThinkList ;
static ConVar report_clientthinklist ( " report_clientthinklist " , " 0 " , FCVAR_CHEAT , " List all clientside entities thinking and time - will report and turn itself off. " ) ;
CClientThinkList : : CClientThinkList ( )
{
}
CClientThinkList : : ~ CClientThinkList ( )
{
}
//-----------------------------------------------------------------------------
// Methods of IGameSystem
//-----------------------------------------------------------------------------
bool CClientThinkList : : Init ( )
{
m_nIterEnum = 0 ;
m_bInThinkLoop = false ;
return true ;
}
void CClientThinkList : : Shutdown ( )
{
}
void CClientThinkList : : LevelInitPreEntity ( )
{
m_nIterEnum = 0 ;
}
void CClientThinkList : : LevelShutdownPreEntity ( )
{
}
void CClientThinkList : : LevelShutdownPostEntity ( )
{
}
void CClientThinkList : : PreRender ( )
{
}
void CClientThinkList : : Update ( float frametime )
{
}
//-----------------------------------------------------------------------------
// Sets the client think
//-----------------------------------------------------------------------------
void CClientThinkList : : SetNextClientThink ( ClientThinkHandle_t hThink , float flNextTime )
{
if ( hThink = = INVALID_THINK_HANDLE )
return ;
if ( m_bInThinkLoop )
{
// Queue up all changes
int i = m_aChangeList . AddToTail ( ) ;
m_aChangeList [ i ] . m_hEnt = INVALID_CLIENTENTITY_HANDLE ;
m_aChangeList [ i ] . m_hThink = hThink ;
m_aChangeList [ i ] . m_flNextTime = flNextTime ;
return ;
}
if ( flNextTime = = CLIENT_THINK_NEVER )
{
RemoveThinkable ( hThink ) ;
}
else
{
GetThinkEntry ( hThink ) - > m_flNextClientThink = flNextTime ;
}
}
void CClientThinkList : : SetNextClientThink ( ClientEntityHandle_t hEnt , float flNextTime )
{
if ( flNextTime = = CLIENT_THINK_NEVER )
{
RemoveThinkable ( hEnt ) ;
return ;
}
IClientThinkable * pThink = ClientEntityList ( ) . GetClientThinkableFromHandle ( hEnt ) ;
if ( ! pThink )
return ;
ClientThinkHandle_t hThink = pThink - > GetThinkHandle ( ) ;
if ( m_bInThinkLoop )
{
// Queue up all changes
int i = m_aChangeList . AddToTail ( ) ;
m_aChangeList [ i ] . m_hEnt = hEnt ;
m_aChangeList [ i ] . m_hThink = hThink ;
m_aChangeList [ i ] . m_flNextTime = flNextTime ;
return ;
}
// Add it to the list if it's not already in there.
if ( hThink = = INVALID_THINK_HANDLE )
{
hThink = ( ClientThinkHandle_t ) m_ThinkEntries . AddToTail ( ) ;
pThink - > SetThinkHandle ( hThink ) ;
ThinkEntry_t * pEntry = GetThinkEntry ( hThink ) ;
pEntry - > m_hEnt = hEnt ;
pEntry - > m_nIterEnum = - 1 ;
pEntry - > m_flLastClientThink = 0.0f ;
}
Assert ( GetThinkEntry ( hThink ) - > m_hEnt = = hEnt ) ;
GetThinkEntry ( hThink ) - > m_flNextClientThink = flNextTime ;
}
//-----------------------------------------------------------------------------
// Removes the thinkable from the list
//-----------------------------------------------------------------------------
void CClientThinkList : : RemoveThinkable ( ClientThinkHandle_t hThink )
{
if ( hThink = = INVALID_THINK_HANDLE )
return ;
if ( m_bInThinkLoop )
{
// Queue up all changes
int i = m_aChangeList . AddToTail ( ) ;
m_aChangeList [ i ] . m_hEnt = INVALID_CLIENTENTITY_HANDLE ;
m_aChangeList [ i ] . m_hThink = hThink ;
m_aChangeList [ i ] . m_flNextTime = CLIENT_THINK_NEVER ;
return ;
}
ThinkEntry_t * pEntry = GetThinkEntry ( hThink ) ;
IClientThinkable * pThink = ClientEntityList ( ) . GetClientThinkableFromHandle ( pEntry - > m_hEnt ) ;
if ( pThink )
{
pThink - > SetThinkHandle ( INVALID_THINK_HANDLE ) ;
}
m_ThinkEntries . Remove ( ( unsigned long ) hThink ) ;
}
//-----------------------------------------------------------------------------
// Removes the thinkable from the list
//-----------------------------------------------------------------------------
void CClientThinkList : : RemoveThinkable ( ClientEntityHandle_t hEnt )
{
IClientThinkable * pThink = ClientEntityList ( ) . GetClientThinkableFromHandle ( hEnt ) ;
if ( pThink )
{
ClientThinkHandle_t hThink = pThink - > GetThinkHandle ( ) ;
if ( hThink ! = INVALID_THINK_HANDLE )
{
Assert ( GetThinkEntry ( hThink ) - > m_hEnt = = hEnt ) ;
RemoveThinkable ( hThink ) ;
}
}
}
//-----------------------------------------------------------------------------
// Performs the think function
//-----------------------------------------------------------------------------
void CClientThinkList : : PerformThinkFunction ( ThinkEntry_t * pEntry , float flCurtime )
{
IClientThinkable * pThink = ClientEntityList ( ) . GetClientThinkableFromHandle ( pEntry - > m_hEnt ) ;
if ( ! pThink )
{
RemoveThinkable ( pEntry - > m_hEnt ) ;
return ;
}
if ( pEntry - > m_flNextClientThink = = CLIENT_THINK_ALWAYS )
{
// NOTE: The Think function here could call SetNextClientThink
// which would cause it to be removed + readded into the list
pThink - > ClientThink ( ) ;
}
else if ( pEntry - > m_flNextClientThink = = FLT_MAX )
{
// This is an entity that doesn't need to think again; remove it
RemoveThinkable ( pEntry - > m_hEnt ) ;
}
else
{
Assert ( pEntry - > m_flNextClientThink < = flCurtime ) ;
// Indicate we're not going to think again
pEntry - > m_flNextClientThink = FLT_MAX ;
// NOTE: The Think function here could call SetNextClientThink
// which would cause it to be readded into the list
pThink - > ClientThink ( ) ;
}
// Set this after the Think calls in case they look at LastClientThink
pEntry - > m_flLastClientThink = flCurtime ;
}
//-----------------------------------------------------------------------------
// Add entity to frame think list
//-----------------------------------------------------------------------------
void CClientThinkList : : AddEntityToFrameThinkList ( ThinkEntry_t * pEntry , bool bAlwaysChain , int & nCount , ThinkEntry_t * * ppFrameThinkList )
{
// We may already have processed this owing to hierarchy rules
if ( pEntry - > m_nIterEnum = = m_nIterEnum )
return ;
// If we're not thinking this frame, we don't have to worry about thinking after our parents
bool bThinkThisInterval = ( pEntry - > m_flNextClientThink = = CLIENT_THINK_ALWAYS ) | |
( pEntry - > m_flNextClientThink < = gpGlobals - > curtime ) ;
// This logic makes it so that if a child thinks,
// *all* hierarchical parents + grandparents will think first, even if some
// of the parents don't need to think this frame
if ( ! bThinkThisInterval & & ! bAlwaysChain )
return ;
// Respect hierarchy
C_BaseEntity * pEntity = ClientEntityList ( ) . GetBaseEntityFromHandle ( pEntry - > m_hEnt ) ;
if ( pEntity )
{
C_BaseEntity * pParent = pEntity - > GetMoveParent ( ) ;
if ( pParent & & ( pParent - > GetThinkHandle ( ) ! = INVALID_THINK_HANDLE ) )
{
ThinkEntry_t * pParentEntry = GetThinkEntry ( pParent - > GetThinkHandle ( ) ) ;
AddEntityToFrameThinkList ( pParentEntry , true , nCount , ppFrameThinkList ) ;
}
}
if ( ! bThinkThisInterval )
return ;
// Add the entry into the list
pEntry - > m_nIterEnum = m_nIterEnum ;
ppFrameThinkList [ nCount + + ] = pEntry ;
}
//-----------------------------------------------------------------------------
// Think for all entities that need it
//-----------------------------------------------------------------------------
void CClientThinkList : : PerformThinkFunctions ( )
{
VPROF_ ( " Client Thinks " , 1 , VPROF_BUDGETGROUP_CLIENT_SIM , false , BUDGETFLAG_CLIENT ) ;
int nMaxList = m_ThinkEntries . Count ( ) ;
if ( nMaxList = = 0 )
return ;
+ + m_nIterEnum ;
// Build a list of entities to think this frame, in order of hierarchy.
// Do this because the list may be modified during the thinking and also to
// prevent bad situations where an entity can think more than once in a frame.
ThinkEntry_t * * ppThinkEntryList = ( ThinkEntry_t * * ) stackalloc ( nMaxList * sizeof ( ThinkEntry_t * ) ) ;
int nThinkCount = 0 ;
for ( unsigned short iCur = m_ThinkEntries . Head ( ) ; iCur ! = m_ThinkEntries . InvalidIndex ( ) ; iCur = m_ThinkEntries . Next ( iCur ) )
{
AddEntityToFrameThinkList ( & m_ThinkEntries [ iCur ] , false , nThinkCount , ppThinkEntryList ) ;
Assert ( nThinkCount < = nMaxList ) ;
}
// While we're in the loop, no changes to the think list are allowed
m_bInThinkLoop = true ;
if ( ! report_clientthinklist . GetBool ( ) )
{
// Perform thinks on all entities that need it
for ( int i = 0 ; i < nThinkCount ; + + i )
{
PerformThinkFunction ( ppThinkEntryList [ i ] , gpGlobals - > curtime ) ;
}
}
else
{
CFastTimer fastTimer ;
for ( int i = 0 ; i < nThinkCount ; + + i )
{
fastTimer . Start ( ) ;
PerformThinkFunction ( ppThinkEntryList [ i ] , gpGlobals - > curtime ) ;
fastTimer . End ( ) ;
C_BaseEntity * pEntity = ClientEntityList ( ) . GetBaseEntityFromHandle ( ppThinkEntryList [ i ] - > m_hEnt ) ;
if ( pEntity )
{
Msg ( " Entity(%d): %s - %f \n " , pEntity - > entindex ( ) , pEntity - > GetDebugName ( ) , fastTimer . GetDuration ( ) . GetMillisecondsF ( ) ) ;
}
}
report_clientthinklist . SetValue ( 0 ) ;
}
m_bInThinkLoop = false ;
// Apply changes to the think list
int nCount = m_aChangeList . Count ( ) ;
for ( int i = 0 ; i < nCount ; + + i )
{
ClientThinkHandle_t hThink = m_aChangeList [ i ] . m_hThink ;
if ( hThink ! = INVALID_THINK_HANDLE )
{
// This can happen if the same think handle was removed twice
if ( ! m_ThinkEntries . IsInList ( ( unsigned long ) hThink ) )
continue ;
// NOTE: This is necessary for the case where the client entity handle
// is slammed to NULL during a think interval; the hThink will get stuck
// in the list and can never leave.
SetNextClientThink ( hThink , m_aChangeList [ i ] . m_flNextTime ) ;
}
else
{
SetNextClientThink ( m_aChangeList [ i ] . m_hEnt , m_aChangeList [ i ] . m_flNextTime ) ;
}
}
m_aChangeList . RemoveAll ( ) ;
// Clear out the client-side entity deletion list.
CleanUpDeleteList ( ) ;
}
//-----------------------------------------------------------------------------
// Queued-up entity deletion
//-----------------------------------------------------------------------------
void CClientThinkList : : AddToDeleteList ( ClientEntityHandle_t hEnt )
{
// Sanity check!
Assert ( hEnt ! = ClientEntityList ( ) . InvalidHandle ( ) ) ;
if ( hEnt = = ClientEntityList ( ) . InvalidHandle ( ) )
return ;
// Check to see if entity is networkable -- don't let it release!
C_BaseEntity * pEntity = ClientEntityList ( ) . GetBaseEntityFromHandle ( hEnt ) ;
if ( pEntity )
{
// Check to see if the entity is already being removed!
if ( pEntity - > IsMarkedForDeletion ( ) )
return ;
// Don't add networkable entities to delete list -- the server should
// take care of this. The delete list is for client-side only entities.
if ( ! pEntity - > GetClientNetworkable ( ) )
{
m_aDeleteList . AddToTail ( hEnt ) ;
pEntity - > SetRemovalFlag ( true ) ;
}
}
}
void CClientThinkList : : RemoveFromDeleteList ( ClientEntityHandle_t hEnt )
{
// Sanity check!
Assert ( hEnt ! = ClientEntityList ( ) . InvalidHandle ( ) ) ;
if ( hEnt = = ClientEntityList ( ) . InvalidHandle ( ) )
return ;
int nSize = m_aDeleteList . Count ( ) ;
for ( int iHandle = 0 ; iHandle < nSize ; + + iHandle )
{
if ( m_aDeleteList [ iHandle ] = = hEnt )
{
m_aDeleteList [ iHandle ] = ClientEntityList ( ) . InvalidHandle ( ) ;
C_BaseEntity * pEntity = ClientEntityList ( ) . GetBaseEntityFromHandle ( hEnt ) ;
if ( pEntity )
{
pEntity - > SetRemovalFlag ( false ) ;
}
}
}
}
void CClientThinkList : : CleanUpDeleteList ( )
{
int nThinkCount = m_aDeleteList . Count ( ) ;
for ( int iThink = 0 ; iThink < nThinkCount ; + + iThink )
{
ClientEntityHandle_t handle = m_aDeleteList [ iThink ] ;
if ( handle ! = ClientEntityList ( ) . InvalidHandle ( ) )
{
C_BaseEntity * pEntity = ClientEntityList ( ) . GetBaseEntityFromHandle ( handle ) ;
if ( pEntity )
{
pEntity - > SetRemovalFlag ( false ) ;
}
IClientThinkable * pThink = ClientEntityList ( ) . GetClientThinkableFromHandle ( handle ) ;
if ( pThink )
{
pThink - > Release ( ) ;
}
}
}
m_aDeleteList . RemoveAll ( ) ;
}