//========= Copyright © 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(); }