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.

338 lines
9.4 KiB

  1. //========= Copyright � 1996-2008, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. #include "cbase.h"
  9. #include "querycache.h"
  10. #include "tier0/vprof.h"
  11. #include "tier1/utlintrusivelist.h"
  12. #include "datacache/imdlcache.h"
  13. #include "vstdlib/jobthread.h"
  14. // memdbgon must be the last include file in a .cpp file!!!
  15. #include "tier0/memdbgon.h"
  16. #define QUERYCACHE_SIZE 1024
  17. static QueryCacheEntry_t s_QCache[QUERYCACHE_SIZE];
  18. #define QUERYCACHE_HASH_SIZE ( QUERYCACHE_SIZE * 2 )
  19. // elements available for cache reuse
  20. static CUtlIntrusiveDList<QueryCacheEntry_t> s_VictimList;
  21. static CUtlIntrusiveDList<QueryCacheEntry_t> s_HashChains[QUERYCACHE_HASH_SIZE];
  22. static int s_nReplaceCtr = QUERYCACHE_SIZE - 1;
  23. static int s_nTimeStampCounter = 0 ;
  24. static int s_nNumCacheQueries = 0;
  25. static int s_nNumCacheMisses = 0;
  26. static int s_SuccessfulSpeculatives = 0;
  27. static int s_WastedSpeculativeUpdates = 0;
  28. void QueryCacheKey_t::ComputeHashIndex( void )
  29. {
  30. unsigned int ret = ( unsigned int ) m_Type;
  31. for( int i = 0 ; i < m_nNumValidPoints; i++ )
  32. {
  33. ret += ( unsigned int ) m_pEntities[i].ToInt();
  34. ret += size_cast< unsigned int >( (uintp)m_nOffsetMode );
  35. }
  36. ret += *( ( uint32 *) &m_flMinimumUpdateInterval );
  37. ret += m_nTraceMask;
  38. m_nHashIdx = ret % QUERYCACHE_HASH_SIZE;
  39. }
  40. ConVar sv_disable_querycache("sv_disable_querycache", "0", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "debug - disable trace query cache" );
  41. static QueryCacheEntry_t *FindOrAllocateCacheEntry( QueryCacheKey_t const &entry )
  42. {
  43. QueryCacheEntry_t *pFound = NULL;
  44. // see if we find it
  45. for( QueryCacheEntry_t *pNode = s_HashChains[entry.m_nHashIdx].m_pHead; pNode; pNode = pNode->m_pNext )
  46. {
  47. if ( pNode->m_QueryParams.Matches( &entry ) )
  48. {
  49. pFound = pNode;
  50. break;
  51. }
  52. }
  53. if (! pFound )
  54. {
  55. pFound = s_VictimList.RemoveHead();
  56. if ( ! pFound )
  57. {
  58. // randomly replace one
  59. pFound = s_QCache + s_nReplaceCtr;
  60. s_nReplaceCtr--;
  61. if ( s_nReplaceCtr < 0 )
  62. s_nReplaceCtr = QUERYCACHE_SIZE - 1;
  63. if ( pFound->m_QueryParams.m_Type != EQUERY_INVALID )
  64. {
  65. s_HashChains[pFound->m_QueryParams.m_nHashIdx].RemoveNode( pFound );
  66. }
  67. }
  68. pFound->m_QueryParams = entry;
  69. s_HashChains[pFound->m_QueryParams.m_nHashIdx].AddToHead( pFound );
  70. pFound->m_bSpeculativelyDone = false;
  71. pFound->IssueQuery();
  72. }
  73. else
  74. {
  75. if ( sv_disable_querycache.GetInt() ||
  76. ( gpGlobals->curtime - pFound->m_flLastUpdateTime >=
  77. pFound->m_QueryParams.m_flMinimumUpdateInterval ) )
  78. {
  79. pFound->m_bSpeculativelyDone = false;
  80. pFound->IssueQuery();
  81. }
  82. else
  83. {
  84. if ( pFound->m_bSpeculativelyDone )
  85. s_SuccessfulSpeculatives++;
  86. }
  87. }
  88. return pFound;
  89. }
  90. static QueryCacheEntry_t *FindOrAllocateCacheEntry( EQueryType_t nType,
  91. CBaseEntity *pEntity1, CBaseEntity *pEntity2,
  92. EEntityOffsetMode_t nMode1, EEntityOffsetMode_t nMode2,
  93. unsigned int nTraceMask )
  94. {
  95. QueryCacheKey_t entry;
  96. entry.m_Type = nType;
  97. entry.m_pEntities[0] = pEntity1;
  98. entry.m_pEntities[1] = pEntity2;
  99. entry.m_nOffsetMode[0] = nMode1;
  100. entry.m_nOffsetMode[1] = nMode2;
  101. entry.m_nTraceMask = nTraceMask;
  102. entry.m_nNumValidPoints = 2;
  103. entry.ComputeHashIndex();
  104. return FindOrAllocateCacheEntry( entry );
  105. }
  106. bool QueryCacheKey_t::Matches( QueryCacheKey_t const *pNode ) const
  107. {
  108. if (
  109. ( pNode->m_Type != m_Type ) ||
  110. ( pNode->m_nTraceMask != m_nTraceMask ) ||
  111. ( pNode->m_pTraceFilterFunction != m_pTraceFilterFunction ) ||
  112. ( pNode->m_nNumValidPoints != m_nNumValidPoints ) ||
  113. ( pNode->m_flMinimumUpdateInterval != m_flMinimumUpdateInterval )
  114. )
  115. return false;
  116. for( int i = 0; i < m_nNumValidPoints; i++ )
  117. {
  118. if (
  119. ( pNode->m_pEntities[i] != m_pEntities[i] ) ||
  120. ( pNode->m_nOffsetMode[i] != m_nOffsetMode[i] )
  121. )
  122. return false;
  123. }
  124. return true;
  125. }
  126. static void CalculateOffsettedPosition( CBaseEntity *pEntity, EEntityOffsetMode_t nMode, Vector *pVecOut )
  127. {
  128. switch( nMode )
  129. {
  130. case EOFFSET_MODE_WORLDSPACE_CENTER:
  131. *pVecOut = pEntity->WorldSpaceCenter();
  132. break;
  133. case EOFFSET_MODE_EYEPOSITION:
  134. *pVecOut = pEntity->EyePosition();
  135. break;
  136. case EOFFSET_MODE_NONE:
  137. pVecOut->Init();
  138. break;
  139. }
  140. }
  141. struct QueryCacheUpdateRecord_t
  142. {
  143. int m_nStartHashChain;
  144. int m_nNumHashChainsToUpdate;
  145. CUtlIntrusiveDListWithTailPtr<QueryCacheEntry_t> m_KilledList;
  146. };
  147. void ProcessQueryCacheUpdate( QueryCacheUpdateRecord_t &workItem )
  148. {
  149. float flCurTime = gpGlobals->curtime;
  150. // run through all of the cache.
  151. for( int i = 0; i < workItem.m_nNumHashChainsToUpdate; i++ )
  152. {
  153. QueryCacheEntry_t *pNext;
  154. for( QueryCacheEntry_t *pEntry = s_HashChains[i + workItem.m_nStartHashChain].m_pHead ; pEntry; pEntry = pNext )
  155. {
  156. pNext = pEntry->m_pNext;
  157. if ( pEntry->m_bUsedSinceUpdated )
  158. {
  159. if ( flCurTime - pEntry->m_flLastUpdateTime >=
  160. pEntry->m_QueryParams.m_flMinimumUpdateInterval )
  161. {
  162. // don't bother updating if we have recently
  163. pEntry->IssueQuery();
  164. pEntry->m_bUsedSinceUpdated = false;
  165. pEntry->m_bSpeculativelyDone = true;
  166. }
  167. }
  168. else
  169. {
  170. if ( flCurTime - pEntry->m_flLastUpdateTime > pEntry->m_QueryParams.m_flMinimumUpdateInterval )
  171. {
  172. if ( pEntry->m_bSpeculativelyDone && ( !pEntry->m_bUsedSinceUpdated ) )
  173. {
  174. s_WastedSpeculativeUpdates++;
  175. }
  176. pEntry->m_QueryParams.m_Type = EQUERY_INVALID;
  177. s_HashChains[pEntry->m_QueryParams.m_nHashIdx].RemoveNode( pEntry );
  178. workItem.m_KilledList.AddToHead( pEntry );
  179. }
  180. }
  181. }
  182. }
  183. }
  184. #define N_WAYS_TO_SPLIT_CACHE_UPDATE 8
  185. static void PreUpdateQueryCache()
  186. {
  187. mdlcache->BeginCoarseLock();
  188. mdlcache->BeginLock();
  189. }
  190. static void PostUpdateQueryCache()
  191. {
  192. mdlcache->EndLock();
  193. mdlcache->EndCoarseLock();
  194. }
  195. void UpdateQueryCache( void )
  196. {
  197. // parallel process all hash chains
  198. QueryCacheUpdateRecord_t workList[N_WAYS_TO_SPLIT_CACHE_UPDATE];
  199. int nCurEntry = 0;
  200. for( int i =0 ; i < N_WAYS_TO_SPLIT_CACHE_UPDATE; i++ )
  201. {
  202. workList[i].m_nStartHashChain = nCurEntry;
  203. if ( i != N_WAYS_TO_SPLIT_CACHE_UPDATE -1 )
  204. workList[i].m_nNumHashChainsToUpdate = ARRAYSIZE( s_HashChains ) / N_WAYS_TO_SPLIT_CACHE_UPDATE;
  205. else
  206. workList[i].m_nNumHashChainsToUpdate = ARRAYSIZE( s_HashChains ) - nCurEntry;
  207. nCurEntry += ARRAYSIZE( s_HashChains ) / N_WAYS_TO_SPLIT_CACHE_UPDATE;
  208. }
  209. ParallelProcess( workList, N_WAYS_TO_SPLIT_CACHE_UPDATE, ProcessQueryCacheUpdate, PreUpdateQueryCache, PostUpdateQueryCache, ( sv_disable_querycache.GetBool() ) ? 0 : INT_MAX );
  210. // now, we need to take all of the obsolete cache entries each thread generated and add them to
  211. // the victim cache
  212. for( int i = 0 ; i < N_WAYS_TO_SPLIT_CACHE_UPDATE; i++ )
  213. {
  214. PrependDListWithTailToDList( workList[i].m_KilledList, s_VictimList );
  215. }
  216. }
  217. void InvalidateQueryCache( void )
  218. {
  219. s_VictimList.RemoveAll();
  220. for( int i = 0; i < ARRAYSIZE( s_HashChains); i++ )
  221. s_HashChains[i].RemoveAll();
  222. // now, invalidate all cache entries and add them to the victims
  223. for( int i = 0; i < ARRAYSIZE( s_QCache ); i++ )
  224. {
  225. s_QCache[i].m_QueryParams.m_Type = EQUERY_INVALID;
  226. s_VictimList.AddToHead( s_QCache + i );
  227. }
  228. }
  229. void QueryCacheEntry_t::IssueQuery( void )
  230. {
  231. for( int i = 0 ; i < m_QueryParams.m_nNumValidPoints; i++ )
  232. {
  233. CBaseEntity *pEntity = m_QueryParams.m_pEntities[i];
  234. if (! pEntity )
  235. {
  236. m_QueryParams.m_Type = EQUERY_INVALID;
  237. s_HashChains[m_QueryParams.m_nHashIdx].RemoveNode( this );
  238. s_VictimList.AddToHead( this );
  239. return;
  240. }
  241. CalculateOffsettedPosition( pEntity, m_QueryParams.m_nOffsetMode[i],
  242. &( m_QueryParams.m_Points[i] ) );
  243. }
  244. CTraceFilterSimple filter( m_QueryParams.m_pEntities[2],
  245. m_QueryParams.m_nCollisionGroup,
  246. m_QueryParams.m_pTraceFilterFunction );
  247. trace_t result;
  248. s_nNumCacheMisses++;
  249. UTIL_TraceLine( m_QueryParams.m_Points[0], m_QueryParams.m_Points[1],
  250. m_QueryParams.m_nTraceMask, &filter, &result );
  251. m_bResult = ! ( result.DidHit() );
  252. m_flLastUpdateTime = gpGlobals->curtime;
  253. }
  254. bool IsLineOfSightBetweenTwoEntitiesClear( CBaseEntity *pSrcEntity,
  255. EEntityOffsetMode_t nSrcOffsetMode,
  256. CBaseEntity *pDestEntity,
  257. EEntityOffsetMode_t nDestOffsetMode,
  258. CBaseEntity *pSkipEntity,
  259. int nCollisionGroup,
  260. unsigned int nTraceMask,
  261. ShouldHitFunc_t pTraceFilterCallback,
  262. float flMinimumUpdateInterval )
  263. {
  264. QueryCacheKey_t entry;
  265. entry.m_Type = EQUERY_ENTITY_LOS_CHECK;
  266. entry.m_pEntities[0] = pSrcEntity;
  267. entry.m_pEntities[1] = pDestEntity;
  268. entry.m_pEntities[2] = pSkipEntity;
  269. entry.m_nOffsetMode[0] = nSrcOffsetMode;
  270. entry.m_nOffsetMode[1] = nDestOffsetMode;
  271. entry.m_nOffsetMode[2] = EOFFSET_MODE_NONE;
  272. entry.m_nTraceMask = nTraceMask;
  273. entry.m_nNumValidPoints = 3;
  274. entry.m_nCollisionGroup = nCollisionGroup;
  275. entry.m_pTraceFilterFunction = pTraceFilterCallback;
  276. entry.m_flMinimumUpdateInterval = flMinimumUpdateInterval;
  277. entry.ComputeHashIndex();
  278. s_nNumCacheQueries++;
  279. QueryCacheEntry_t *pNode = FindOrAllocateCacheEntry( entry );
  280. pNode->m_bUsedSinceUpdated = true;
  281. return pNode->m_bResult;
  282. }
  283. #if defined( CLIENT_DLL )
  284. CON_COMMAND_F( cl_querycache_stats, "Display status of the query cache (client only)", FCVAR_CHEAT )
  285. #else
  286. CON_COMMAND( sv_querycache_stats, "Display status of the query cache (client only)" )
  287. #endif
  288. {
  289. Warning( "%d queries, %d misses (%d free) suc spec = %d wasted spec=%d\n",
  290. s_nNumCacheQueries, s_nNumCacheMisses, s_VictimList.Count(),
  291. s_SuccessfulSpeculatives, s_WastedSpeculativeUpdates );
  292. }