Team Fortress 2 Source Code as on 22/4/2020
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.

402 lines
11 KiB

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