Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

426 lines
12 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "tier0/vprof.h"
  9. // memdbgon must be the last include file in a .cpp file!!!
  10. #include "tier0/memdbgon.h"
  11. CClientThinkList g_ClientThinkList;
  12. static ConVar report_clientthinklist( "report_clientthinklist", "0", FCVAR_CHEAT, "List all clientside entities thinking and time - will report and turn itself off." );
  13. CClientThinkList::CClientThinkList()
  14. {
  15. }
  16. CClientThinkList::~CClientThinkList()
  17. {
  18. }
  19. //-----------------------------------------------------------------------------
  20. // Methods of IGameSystem
  21. //-----------------------------------------------------------------------------
  22. bool CClientThinkList::Init()
  23. {
  24. m_nIterEnum = 0;
  25. m_bInThinkLoop = false;
  26. return true;
  27. }
  28. void CClientThinkList::Shutdown()
  29. {
  30. }
  31. void CClientThinkList::LevelInitPreEntity()
  32. {
  33. m_nIterEnum = 0;
  34. }
  35. void CClientThinkList::LevelShutdownPreEntity()
  36. {
  37. }
  38. void CClientThinkList::LevelShutdownPostEntity()
  39. {
  40. }
  41. void CClientThinkList::PreRender()
  42. {
  43. }
  44. void CClientThinkList::Update( float frametime )
  45. {
  46. }
  47. //-----------------------------------------------------------------------------
  48. // Sets the client think
  49. //-----------------------------------------------------------------------------
  50. void CClientThinkList::SetNextClientThink( ClientThinkHandle_t hThink, float flNextTime )
  51. {
  52. if ( hThink == INVALID_THINK_HANDLE )
  53. return;
  54. if ( m_bInThinkLoop )
  55. {
  56. // Queue up all changes
  57. int i = m_aChangeList.AddToTail();
  58. m_aChangeList[i].m_hEnt = INVALID_CLIENTENTITY_HANDLE;
  59. m_aChangeList[i].m_hThink = hThink;
  60. m_aChangeList[i].m_flNextTime = flNextTime;
  61. return;
  62. }
  63. if ( flNextTime == CLIENT_THINK_NEVER )
  64. {
  65. RemoveThinkable( hThink );
  66. }
  67. else
  68. {
  69. GetThinkEntry( hThink )->m_flNextClientThink = flNextTime;
  70. }
  71. }
  72. void CClientThinkList::SetNextClientThink( ClientEntityHandle_t hEnt, float flNextTime )
  73. {
  74. if ( flNextTime == CLIENT_THINK_NEVER )
  75. {
  76. RemoveThinkable( hEnt );
  77. return;
  78. }
  79. IClientThinkable *pThink = ClientEntityList().GetClientThinkableFromHandle( hEnt );
  80. if ( !pThink )
  81. return;
  82. ClientThinkHandle_t hThink = pThink->GetThinkHandle();
  83. if ( m_bInThinkLoop )
  84. {
  85. // Queue up all changes
  86. int i = m_aChangeList.AddToTail();
  87. m_aChangeList[i].m_hEnt = hEnt;
  88. m_aChangeList[i].m_hThink = hThink;
  89. m_aChangeList[i].m_flNextTime = flNextTime;
  90. return;
  91. }
  92. // Add it to the list if it's not already in there.
  93. if ( hThink == INVALID_THINK_HANDLE )
  94. {
  95. hThink = (ClientThinkHandle_t)(uintp)m_ThinkEntries.AddToTail();
  96. pThink->SetThinkHandle( hThink );
  97. ThinkEntry_t *pEntry = GetThinkEntry( hThink );
  98. pEntry->m_hEnt = hEnt;
  99. pEntry->m_nIterEnum = -1;
  100. pEntry->m_flLastClientThink = 0.0f;
  101. }
  102. Assert( GetThinkEntry( hThink )->m_hEnt == hEnt );
  103. GetThinkEntry( hThink )->m_flNextClientThink = flNextTime;
  104. }
  105. //-----------------------------------------------------------------------------
  106. // Removes the thinkable from the list
  107. //-----------------------------------------------------------------------------
  108. void CClientThinkList::RemoveThinkable( ClientThinkHandle_t hThink )
  109. {
  110. if ( hThink == INVALID_THINK_HANDLE )
  111. return;
  112. if ( m_bInThinkLoop )
  113. {
  114. // Queue up all changes
  115. int i = m_aChangeList.AddToTail();
  116. m_aChangeList[i].m_hEnt = INVALID_CLIENTENTITY_HANDLE;
  117. m_aChangeList[i].m_hThink = hThink;
  118. m_aChangeList[i].m_flNextTime = CLIENT_THINK_NEVER;
  119. return;
  120. }
  121. ThinkEntry_t *pEntry = GetThinkEntry( hThink );
  122. IClientThinkable *pThink = ClientEntityList().GetClientThinkableFromHandle( pEntry->m_hEnt );
  123. if ( pThink )
  124. {
  125. pThink->SetThinkHandle( INVALID_THINK_HANDLE );
  126. }
  127. m_ThinkEntries.Remove( (unsigned long)hThink );
  128. }
  129. //-----------------------------------------------------------------------------
  130. // Removes the thinkable from the list
  131. //-----------------------------------------------------------------------------
  132. void CClientThinkList::RemoveThinkable( ClientEntityHandle_t hEnt )
  133. {
  134. IClientThinkable *pThink = ClientEntityList().GetClientThinkableFromHandle( hEnt );
  135. if ( pThink )
  136. {
  137. ClientThinkHandle_t hThink = pThink->GetThinkHandle();
  138. if ( hThink != INVALID_THINK_HANDLE )
  139. {
  140. Assert( GetThinkEntry( hThink )->m_hEnt == hEnt );
  141. RemoveThinkable( hThink );
  142. }
  143. }
  144. }
  145. //-----------------------------------------------------------------------------
  146. // Performs the think function
  147. //-----------------------------------------------------------------------------
  148. void CClientThinkList::PerformThinkFunction( ThinkEntry_t *pEntry, float flCurtime )
  149. {
  150. IClientThinkable *pThink = ClientEntityList().GetClientThinkableFromHandle( pEntry->m_hEnt );
  151. if ( !pThink )
  152. {
  153. RemoveThinkable( pEntry->m_hEnt );
  154. return;
  155. }
  156. if ( pEntry->m_flNextClientThink == CLIENT_THINK_ALWAYS )
  157. {
  158. // NOTE: The Think function here could call SetNextClientThink
  159. // which would cause it to be removed + readded into the list
  160. pThink->ClientThink();
  161. }
  162. else if ( pEntry->m_flNextClientThink == FLT_MAX )
  163. {
  164. // This is an entity that doesn't need to think again; remove it
  165. RemoveThinkable( pEntry->m_hEnt );
  166. }
  167. else
  168. {
  169. Assert( pEntry->m_flNextClientThink <= flCurtime );
  170. // Indicate we're not going to think again
  171. pEntry->m_flNextClientThink = FLT_MAX;
  172. // NOTE: The Think function here could call SetNextClientThink
  173. // which would cause it to be readded into the list
  174. pThink->ClientThink();
  175. }
  176. // Set this after the Think calls in case they look at LastClientThink
  177. pEntry->m_flLastClientThink = flCurtime;
  178. }
  179. //-----------------------------------------------------------------------------
  180. // Add entity to frame think list
  181. //-----------------------------------------------------------------------------
  182. void CClientThinkList::AddEntityToFrameThinkList( ThinkEntry_t *pEntry, bool bAlwaysChain, int &nCount, ThinkEntry_t **ppFrameThinkList )
  183. {
  184. // We may already have processed this owing to hierarchy rules
  185. if ( pEntry->m_nIterEnum == m_nIterEnum )
  186. return;
  187. // If we're not thinking this frame, we don't have to worry about thinking after our parents
  188. bool bThinkThisInterval = ( pEntry->m_flNextClientThink == CLIENT_THINK_ALWAYS ) ||
  189. ( pEntry->m_flNextClientThink <= gpGlobals->curtime );
  190. // This logic makes it so that if a child thinks,
  191. // *all* hierarchical parents + grandparents will think first, even if some
  192. // of the parents don't need to think this frame
  193. if ( !bThinkThisInterval && !bAlwaysChain )
  194. return;
  195. // Respect hierarchy
  196. C_BaseEntity *pEntity = ClientEntityList().GetBaseEntityFromHandle( pEntry->m_hEnt );
  197. if ( pEntity )
  198. {
  199. C_BaseEntity *pParent = pEntity->GetMoveParent();
  200. if ( pParent && (pParent->GetThinkHandle() != INVALID_THINK_HANDLE) )
  201. {
  202. ThinkEntry_t *pParentEntry = GetThinkEntry( pParent->GetThinkHandle() );
  203. AddEntityToFrameThinkList( pParentEntry, true, nCount, ppFrameThinkList );
  204. }
  205. }
  206. if ( !bThinkThisInterval )
  207. return;
  208. // Add the entry into the list
  209. pEntry->m_nIterEnum = m_nIterEnum;
  210. ppFrameThinkList[nCount++] = pEntry;
  211. }
  212. //-----------------------------------------------------------------------------
  213. // Think for all entities that need it
  214. //-----------------------------------------------------------------------------
  215. void CClientThinkList::PerformThinkFunctions()
  216. {
  217. VPROF_("Client Thinks", 1, VPROF_BUDGETGROUP_CLIENT_SIM, false, BUDGETFLAG_CLIENT);
  218. int nMaxList = m_ThinkEntries.Count();
  219. if ( nMaxList == 0 )
  220. return;
  221. ++m_nIterEnum;
  222. // Build a list of entities to think this frame, in order of hierarchy.
  223. // Do this because the list may be modified during the thinking and also to
  224. // prevent bad situations where an entity can think more than once in a frame.
  225. ThinkEntry_t **ppThinkEntryList = (ThinkEntry_t**)stackalloc( nMaxList * sizeof(ThinkEntry_t*) );
  226. int nThinkCount = 0;
  227. for ( unsigned short iCur=m_ThinkEntries.Head(); iCur != m_ThinkEntries.InvalidIndex(); iCur = m_ThinkEntries.Next( iCur ) )
  228. {
  229. AddEntityToFrameThinkList( &m_ThinkEntries[iCur], false, nThinkCount, ppThinkEntryList );
  230. Assert( nThinkCount <= nMaxList );
  231. }
  232. // While we're in the loop, no changes to the think list are allowed
  233. m_bInThinkLoop = true;
  234. if( !report_clientthinklist.GetBool() )
  235. {
  236. // Perform thinks on all entities that need it
  237. for ( int i = 0; i < nThinkCount; ++i )
  238. {
  239. PerformThinkFunction( ppThinkEntryList[i], gpGlobals->curtime );
  240. }
  241. }
  242. else
  243. {
  244. CFastTimer fastTimer;
  245. for ( int i = 0; i < nThinkCount; ++i )
  246. {
  247. fastTimer.Start();
  248. PerformThinkFunction( ppThinkEntryList[i], gpGlobals->curtime );
  249. fastTimer.End();
  250. C_BaseEntity *pEntity = ClientEntityList().GetBaseEntityFromHandle( ppThinkEntryList[i]->m_hEnt );
  251. if ( pEntity )
  252. {
  253. Msg( "Entity(%d): %s - %f\n", pEntity->entindex(), pEntity->GetDebugName(), fastTimer.GetDuration().GetMillisecondsF() );
  254. }
  255. }
  256. report_clientthinklist.SetValue( 0 );
  257. }
  258. m_bInThinkLoop = false;
  259. // Apply changes to the think list
  260. int nCount = m_aChangeList.Count();
  261. for ( int i = 0; i < nCount; ++i )
  262. {
  263. ClientThinkHandle_t hThink = m_aChangeList[i].m_hThink;
  264. if ( hThink != INVALID_THINK_HANDLE )
  265. {
  266. // This can happen if the same think handle was removed twice
  267. if ( !m_ThinkEntries.IsInList( (unsigned long)hThink ) )
  268. continue;
  269. // NOTE: This is necessary for the case where the client entity handle
  270. // is slammed to NULL during a think interval; the hThink will get stuck
  271. // in the list and can never leave.
  272. SetNextClientThink( hThink, m_aChangeList[i].m_flNextTime );
  273. }
  274. else
  275. {
  276. SetNextClientThink( m_aChangeList[i].m_hEnt, m_aChangeList[i].m_flNextTime );
  277. }
  278. }
  279. m_aChangeList.RemoveAll();
  280. // Clear out the client-side entity deletion list.
  281. CleanUpDeleteList();
  282. }
  283. //-----------------------------------------------------------------------------
  284. // Queued-up entity deletion
  285. //-----------------------------------------------------------------------------
  286. void CClientThinkList::AddToDeleteList( ClientEntityHandle_t hEnt )
  287. {
  288. // Sanity check!
  289. Assert( hEnt != ClientEntityList().InvalidHandle() );
  290. if ( hEnt == ClientEntityList().InvalidHandle() )
  291. return;
  292. // Check to see if entity is networkable -- don't let it release!
  293. C_BaseEntity *pEntity = ClientEntityList().GetBaseEntityFromHandle( hEnt );
  294. if ( pEntity )
  295. {
  296. // Check to see if the entity is already being removed!
  297. if ( pEntity->IsMarkedForDeletion() )
  298. return;
  299. // Don't add networkable entities to delete list -- the server should
  300. // take care of this. The delete list is for client-side only entities.
  301. if ( !pEntity->GetClientNetworkable() )
  302. {
  303. m_aDeleteList.AddToTail( hEnt );
  304. pEntity->SetRemovalFlag( true );
  305. }
  306. }
  307. }
  308. void CClientThinkList::RemoveFromDeleteList( ClientEntityHandle_t hEnt )
  309. {
  310. // Sanity check!
  311. Assert( hEnt != ClientEntityList().InvalidHandle() );
  312. if ( hEnt == ClientEntityList().InvalidHandle() )
  313. return;
  314. int nSize = m_aDeleteList.Count();
  315. for ( int iHandle = 0; iHandle < nSize; ++iHandle )
  316. {
  317. if ( m_aDeleteList[iHandle] == hEnt )
  318. {
  319. m_aDeleteList[iHandle] = ClientEntityList().InvalidHandle();
  320. C_BaseEntity *pEntity = ClientEntityList().GetBaseEntityFromHandle( hEnt );
  321. if ( pEntity )
  322. {
  323. pEntity->SetRemovalFlag( false );
  324. }
  325. }
  326. }
  327. }
  328. void CClientThinkList::CleanUpDeleteList()
  329. {
  330. int nThinkCount = m_aDeleteList.Count();
  331. for ( int iThink = 0; iThink < nThinkCount; ++iThink )
  332. {
  333. ClientEntityHandle_t handle = m_aDeleteList[iThink];
  334. if ( handle != ClientEntityList().InvalidHandle() )
  335. {
  336. C_BaseEntity *pEntity = ClientEntityList().GetBaseEntityFromHandle( handle );
  337. if ( pEntity )
  338. {
  339. pEntity->SetRemovalFlag( false );
  340. }
  341. IClientThinkable *pThink = ClientEntityList().GetClientThinkableFromHandle( handle );
  342. if ( pThink )
  343. {
  344. pThink->Release();
  345. }
  346. }
  347. }
  348. m_aDeleteList.RemoveAll();
  349. }