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.

1385 lines
37 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "datacache/idatacache.h"
  7. #ifdef _LINUX
  8. #include <malloc.h>
  9. #endif
  10. #include "tier0/vprof.h"
  11. #include "basetypes.h"
  12. #include "convar.h"
  13. #include "interface.h"
  14. #include "datamanager.h"
  15. #include "utlrbtree.h"
  16. #include "utlhash.h"
  17. #include "utlmap.h"
  18. #include "generichash.h"
  19. #include "filesystem.h"
  20. #include "datacache.h"
  21. #include "utlvector.h"
  22. #include "fmtstr.h"
  23. // memdbgon must be the last include file in a .cpp file!!!
  24. #include "tier0/memdbgon.h"
  25. //-----------------------------------------------------------------------------
  26. // Singleton
  27. //-----------------------------------------------------------------------------
  28. CDataCache g_DataCache;
  29. EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CDataCache, IDataCache, DATACACHE_INTERFACE_VERSION, g_DataCache );
  30. //-----------------------------------------------------------------------------
  31. //
  32. // Data Cache class implemenations
  33. //
  34. //-----------------------------------------------------------------------------
  35. //-----------------------------------------------------------------------------
  36. // Console commands
  37. //-----------------------------------------------------------------------------
  38. ConVar developer( "developer", "0", FCVAR_INTERNAL_USE );
  39. static ConVar mem_force_flush( "mem_force_flush", "0", FCVAR_CHEAT, "Force cache flush of unlocked resources on every alloc" );
  40. static int g_iDontForceFlush;
  41. //-----------------------------------------------------------------------------
  42. // DataCacheItem_t
  43. //-----------------------------------------------------------------------------
  44. DEFINE_FIXEDSIZE_ALLOCATOR_MT( DataCacheItem_t, 4096/sizeof(DataCacheItem_t), CUtlMemoryPool::GROW_SLOW );
  45. void DataCacheItem_t::DestroyResource()
  46. {
  47. if ( pSection )
  48. {
  49. pSection->DiscardItemData( this, DC_AGE_DISCARD );
  50. }
  51. delete this;
  52. }
  53. //-----------------------------------------------------------------------------
  54. // CDataCacheSection
  55. //-----------------------------------------------------------------------------
  56. CDataCacheSection::CDataCacheSection( CDataCache *pSharedCache, IDataCacheClient *pClient, const char *pszName )
  57. : m_pClient( pClient ),
  58. m_LRU( pSharedCache->m_LRU ),
  59. m_mutex( pSharedCache->m_mutex ),
  60. m_pSharedCache( pSharedCache ),
  61. m_nFrameUnlockCounter( 0 ),
  62. m_options( 0 )
  63. {
  64. memset( &m_status, 0, sizeof(m_status) );
  65. AssertMsg1( strlen(pszName) <= DC_MAX_CLIENT_NAME, "Cache client name too long \"%s\"", pszName );
  66. Q_strncpy( szName, pszName, sizeof(szName) );
  67. for ( int i = 0; i < DC_MAX_THREADS_FRAMELOCKED; i++ )
  68. {
  69. FrameLock_t *pFrameLock = new FrameLock_t;
  70. pFrameLock->m_iThread = i;
  71. m_FreeFrameLocks.Push( pFrameLock );
  72. }
  73. }
  74. CDataCacheSection::~CDataCacheSection()
  75. {
  76. FrameLock_t *pFrameLock;
  77. while ( ( pFrameLock = m_FreeFrameLocks.Pop() ) != NULL )
  78. {
  79. delete pFrameLock;
  80. }
  81. }
  82. //-----------------------------------------------------------------------------
  83. // Purpose: Controls cache size.
  84. //-----------------------------------------------------------------------------
  85. void CDataCacheSection::SetLimits( const DataCacheLimits_t &limits )
  86. {
  87. m_limits = limits;
  88. AssertMsg( m_limits.nMinBytes == 0 && m_limits.nMinItems == 0, "Cache minimums not yet implemented" );
  89. }
  90. //-----------------------------------------------------------------------------
  91. //
  92. //-----------------------------------------------------------------------------
  93. const DataCacheLimits_t &CDataCacheSection::GetLimits()
  94. {
  95. return m_limits;
  96. }
  97. //-----------------------------------------------------------------------------
  98. // Purpose: Controls cache options.
  99. //-----------------------------------------------------------------------------
  100. void CDataCacheSection::SetOptions( unsigned options )
  101. {
  102. m_options = options;
  103. }
  104. //-----------------------------------------------------------------------------
  105. // Purpose: Get the current state of the section
  106. //-----------------------------------------------------------------------------
  107. void CDataCacheSection::GetStatus( DataCacheStatus_t *pStatus, DataCacheLimits_t *pLimits )
  108. {
  109. if ( pStatus )
  110. {
  111. *pStatus = m_status;
  112. }
  113. if ( pLimits )
  114. {
  115. *pLimits = m_limits;
  116. }
  117. }
  118. //-----------------------------------------------------------------------------
  119. //
  120. //-----------------------------------------------------------------------------
  121. void CDataCacheSection::EnsureCapacity( unsigned nBytes, unsigned nItems )
  122. {
  123. VPROF( "CDataCacheSection::EnsureCapacity" );
  124. if ( m_limits.nMaxItems != (unsigned)-1 || m_limits.nMaxBytes != (unsigned)-1 )
  125. {
  126. unsigned nNewSectionBytes = GetNumBytes() + nBytes;
  127. if ( nNewSectionBytes > m_limits.nMaxBytes )
  128. {
  129. Purge( nNewSectionBytes - m_limits.nMaxBytes );
  130. }
  131. if ( GetNumItems() >= m_limits.nMaxItems )
  132. {
  133. PurgeItems( ( GetNumItems() - m_limits.nMaxItems ) + 1 );
  134. }
  135. }
  136. m_pSharedCache->EnsureCapacity( nBytes );
  137. }
  138. //-----------------------------------------------------------------------------
  139. // Purpose: Add an item to the cache. Purges old items if over budget, returns false if item was already in cache.
  140. //-----------------------------------------------------------------------------
  141. bool CDataCacheSection::Add( DataCacheClientID_t clientId, const void *pItemData, unsigned size, DataCacheHandle_t *pHandle )
  142. {
  143. return AddEx( clientId, pItemData, size, DCAF_DEFAULT, pHandle );
  144. }
  145. //-----------------------------------------------------------------------------
  146. // Purpose: Add an item to the cache. Purges old items if over budget, returns false if item was already in cache.
  147. //-----------------------------------------------------------------------------
  148. bool CDataCacheSection::AddEx( DataCacheClientID_t clientId, const void *pItemData, unsigned size, unsigned flags, DataCacheHandle_t *pHandle )
  149. {
  150. VPROF( "CDataCacheSection::Add" );
  151. if ( mem_force_flush.GetBool() )
  152. {
  153. m_pSharedCache->Flush();
  154. }
  155. if ( ( m_options & DC_VALIDATE ) && Find( clientId ) )
  156. {
  157. Error( "Duplicate add to data cache\n" );
  158. return false;
  159. }
  160. EnsureCapacity( size );
  161. DataCacheItemData_t itemData =
  162. {
  163. pItemData,
  164. size,
  165. clientId,
  166. this
  167. };
  168. memhandle_t hMem = m_LRU.CreateResource( itemData, true );
  169. Assert( hMem != (memhandle_t)0 && hMem != (memhandle_t)DC_INVALID_HANDLE );
  170. AccessItem( hMem )->hLRU = hMem;
  171. if ( pHandle )
  172. {
  173. *pHandle = (DataCacheHandle_t)hMem;
  174. }
  175. NoteAdd( size );
  176. OnAdd( clientId, (DataCacheHandle_t)hMem );
  177. g_iDontForceFlush++;
  178. if ( flags & DCAF_LOCK )
  179. {
  180. Lock( (DataCacheHandle_t)hMem );
  181. }
  182. // Add implies a frame lock. A no-op if not in frame lock
  183. FrameLock( (DataCacheHandle_t)hMem );
  184. g_iDontForceFlush--;
  185. m_LRU.UnlockResource( hMem );
  186. return true;
  187. }
  188. //-----------------------------------------------------------------------------
  189. // Purpose: Finds an item in the cache, returns NULL if item is not in cache.
  190. //-----------------------------------------------------------------------------
  191. DataCacheHandle_t CDataCacheSection::Find( DataCacheClientID_t clientId )
  192. {
  193. VPROF( "CDataCacheSection::Find" );
  194. m_status.nFindRequests++;
  195. DataCacheHandle_t hResult = DoFind( clientId );
  196. if ( hResult != DC_INVALID_HANDLE )
  197. {
  198. m_status.nFindHits++;
  199. }
  200. return hResult;
  201. }
  202. //---------------------------------------------------------
  203. DataCacheHandle_t CDataCacheSection::DoFind( DataCacheClientID_t clientId )
  204. {
  205. AUTO_LOCK( m_mutex );
  206. memhandle_t hCurrent;
  207. hCurrent = GetFirstUnlockedItem();
  208. while ( hCurrent != INVALID_MEMHANDLE )
  209. {
  210. if ( AccessItem( hCurrent )->clientId == clientId )
  211. {
  212. m_status.nFindHits++;
  213. return (DataCacheHandle_t)hCurrent;
  214. }
  215. hCurrent = GetNextItem( hCurrent );
  216. }
  217. hCurrent = GetFirstLockedItem();
  218. while ( hCurrent != INVALID_MEMHANDLE )
  219. {
  220. if ( AccessItem( hCurrent )->clientId == clientId )
  221. {
  222. m_status.nFindHits++;
  223. return (DataCacheHandle_t)hCurrent;
  224. }
  225. hCurrent = GetNextItem( hCurrent );
  226. }
  227. return DC_INVALID_HANDLE;
  228. }
  229. //-----------------------------------------------------------------------------
  230. // Purpose: Get an item out of the cache and remove it. No callbacks are executed.
  231. //-----------------------------------------------------------------------------
  232. DataCacheRemoveResult_t CDataCacheSection::Remove( DataCacheHandle_t handle, const void **ppItemData, unsigned *pItemSize, bool bNotify )
  233. {
  234. VPROF( "CDataCacheSection::Remove" );
  235. if ( handle != DC_INVALID_HANDLE )
  236. {
  237. memhandle_t lruHandle = (memhandle_t)handle;
  238. if ( m_LRU.LockCount( lruHandle ) > 0 )
  239. {
  240. return DC_LOCKED;
  241. }
  242. AUTO_LOCK( m_mutex );
  243. DataCacheItem_t *pItem = AccessItem( lruHandle );
  244. if ( pItem )
  245. {
  246. if ( ppItemData )
  247. {
  248. *ppItemData = pItem->pItemData;
  249. }
  250. if ( pItemSize )
  251. {
  252. *pItemSize = pItem->size;
  253. }
  254. DiscardItem( lruHandle, ( bNotify ) ? DC_REMOVED : DC_NONE );
  255. return DC_OK;
  256. }
  257. }
  258. return DC_NOT_FOUND;
  259. }
  260. //-----------------------------------------------------------------------------
  261. //
  262. //-----------------------------------------------------------------------------
  263. bool CDataCacheSection::IsPresent( DataCacheHandle_t handle )
  264. {
  265. return ( m_LRU.GetResource_NoLockNoLRUTouch( (memhandle_t)handle ) != NULL );
  266. }
  267. //-----------------------------------------------------------------------------
  268. // Purpose: Lock an item in the cache, returns NULL if item is not in the cache.
  269. //-----------------------------------------------------------------------------
  270. void *CDataCacheSection::Lock( DataCacheHandle_t handle )
  271. {
  272. VPROF( "CDataCacheSection::Lock" );
  273. if ( mem_force_flush.GetBool() && !g_iDontForceFlush)
  274. Flush();
  275. if ( handle != DC_INVALID_HANDLE )
  276. {
  277. DataCacheItem_t *pItem = m_LRU.LockResource( (memhandle_t)handle );
  278. if ( pItem )
  279. {
  280. if ( m_LRU.LockCount( (memhandle_t)handle ) == 1 )
  281. {
  282. NoteLock( pItem->size );
  283. }
  284. return const_cast<void *>(pItem->pItemData);
  285. }
  286. }
  287. return NULL;
  288. }
  289. //-----------------------------------------------------------------------------
  290. // Purpose: Unlock a previous lock.
  291. //-----------------------------------------------------------------------------
  292. int CDataCacheSection::Unlock( DataCacheHandle_t handle )
  293. {
  294. VPROF( "CDataCacheSection::Unlock" );
  295. int iNewLockCount = 0;
  296. if ( handle != DC_INVALID_HANDLE )
  297. {
  298. AssertMsg( AccessItem( (memhandle_t)handle ) != NULL, "Attempted to unlock nonexistent cache entry" );
  299. unsigned nBytesUnlocked = 0;
  300. m_mutex.Lock();
  301. iNewLockCount = m_LRU.UnlockResource( (memhandle_t)handle );
  302. if ( iNewLockCount == 0 )
  303. {
  304. nBytesUnlocked = AccessItem( (memhandle_t)handle )->size;
  305. }
  306. m_mutex.Unlock();
  307. if ( nBytesUnlocked )
  308. {
  309. NoteUnlock( nBytesUnlocked );
  310. EnsureCapacity( 0 );
  311. }
  312. }
  313. return iNewLockCount;
  314. }
  315. //-----------------------------------------------------------------------------
  316. // Purpose: Lock the mutex
  317. //-----------------------------------------------------------------------------
  318. void CDataCacheSection::LockMutex()
  319. {
  320. g_iDontForceFlush++;
  321. m_mutex.Lock();
  322. }
  323. //-----------------------------------------------------------------------------
  324. // Purpose: Unlock the mutex
  325. //-----------------------------------------------------------------------------
  326. void CDataCacheSection::UnlockMutex()
  327. {
  328. g_iDontForceFlush--;
  329. m_mutex.Unlock();
  330. }
  331. //-----------------------------------------------------------------------------
  332. // Purpose: Get without locking
  333. //-----------------------------------------------------------------------------
  334. void *CDataCacheSection::Get( DataCacheHandle_t handle, bool bFrameLock )
  335. {
  336. VPROF( "CDataCacheSection::Get" );
  337. if ( mem_force_flush.GetBool() && !g_iDontForceFlush)
  338. Flush();
  339. if ( handle != DC_INVALID_HANDLE )
  340. {
  341. if ( bFrameLock && IsFrameLocking() )
  342. return FrameLock( handle );
  343. AUTO_LOCK( m_mutex );
  344. DataCacheItem_t *pItem = m_LRU.GetResource_NoLock( (memhandle_t)handle );
  345. if ( pItem )
  346. {
  347. return const_cast<void *>( pItem->pItemData );
  348. }
  349. }
  350. return NULL;
  351. }
  352. //-----------------------------------------------------------------------------
  353. // Purpose: Get without locking
  354. //-----------------------------------------------------------------------------
  355. void *CDataCacheSection::GetNoTouch( DataCacheHandle_t handle, bool bFrameLock )
  356. {
  357. VPROF( "CDataCacheSection::GetNoTouch" );
  358. if ( handle != DC_INVALID_HANDLE )
  359. {
  360. if ( bFrameLock && IsFrameLocking() )
  361. return FrameLock( handle );
  362. AUTO_LOCK( m_mutex );
  363. DataCacheItem_t *pItem = m_LRU.GetResource_NoLockNoLRUTouch( (memhandle_t)handle );
  364. if ( pItem )
  365. {
  366. return const_cast<void *>( pItem->pItemData );
  367. }
  368. }
  369. return NULL;
  370. }
  371. //-----------------------------------------------------------------------------
  372. // Purpose: "Frame locking" (not game frame). A crude way to manage locks over relatively
  373. // short periods. Does not affect normal locks/unlocks
  374. //-----------------------------------------------------------------------------
  375. int CDataCacheSection::BeginFrameLocking()
  376. {
  377. FrameLock_t *pFrameLock = m_ThreadFrameLock.Get();
  378. if ( pFrameLock )
  379. {
  380. pFrameLock->m_iLock++;
  381. }
  382. else
  383. {
  384. while ( ( pFrameLock = m_FreeFrameLocks.Pop() ) == NULL )
  385. {
  386. ThreadPause();
  387. ThreadSleep( 1 );
  388. }
  389. pFrameLock->m_iLock = 1;
  390. pFrameLock->m_pFirst = NULL;
  391. m_ThreadFrameLock.Set( pFrameLock );
  392. }
  393. return pFrameLock->m_iLock;
  394. }
  395. //-----------------------------------------------------------------------------
  396. //
  397. //-----------------------------------------------------------------------------
  398. bool CDataCacheSection::IsFrameLocking()
  399. {
  400. FrameLock_t *pFrameLock = m_ThreadFrameLock.Get();
  401. return ( pFrameLock != NULL );
  402. }
  403. //-----------------------------------------------------------------------------
  404. //
  405. //-----------------------------------------------------------------------------
  406. void *CDataCacheSection::FrameLock( DataCacheHandle_t handle )
  407. {
  408. VPROF( "CDataCacheSection::FrameLock" );
  409. if ( mem_force_flush.GetBool() && !g_iDontForceFlush)
  410. Flush();
  411. void *pResult = NULL;
  412. FrameLock_t *pFrameLock = m_ThreadFrameLock.Get();
  413. if ( pFrameLock )
  414. {
  415. DataCacheItem_t *pItem = m_LRU.LockResource( (memhandle_t)handle );
  416. if ( pItem )
  417. {
  418. int iThread = pFrameLock->m_iThread;
  419. if ( pItem->pNextFrameLocked[iThread] == DC_NO_NEXT_LOCKED )
  420. {
  421. pItem->pNextFrameLocked[iThread] = pFrameLock->m_pFirst;
  422. pFrameLock->m_pFirst = pItem;
  423. Lock( handle );
  424. }
  425. pResult = const_cast<void *>(pItem->pItemData);
  426. m_LRU.UnlockResource( (memhandle_t)handle );
  427. }
  428. }
  429. return pResult;
  430. }
  431. //-----------------------------------------------------------------------------
  432. //
  433. //-----------------------------------------------------------------------------
  434. int CDataCacheSection::EndFrameLocking()
  435. {
  436. FrameLock_t *pFrameLock = m_ThreadFrameLock.Get();
  437. Assert( pFrameLock->m_iLock > 0 );
  438. if ( pFrameLock->m_iLock == 1 )
  439. {
  440. VPROF( "CDataCacheSection::EndFrameLocking" );
  441. DataCacheItem_t *pItem = pFrameLock->m_pFirst;
  442. DataCacheItem_t *pNext;
  443. int iThread = pFrameLock->m_iThread;
  444. while ( pItem )
  445. {
  446. pNext = pItem->pNextFrameLocked[iThread];
  447. pItem->pNextFrameLocked[iThread] = DC_NO_NEXT_LOCKED;
  448. Unlock( pItem->hLRU );
  449. pItem = pNext;
  450. }
  451. m_FreeFrameLocks.Push( pFrameLock );
  452. m_ThreadFrameLock.Set( NULL );
  453. return 0;
  454. }
  455. else
  456. {
  457. pFrameLock->m_iLock--;
  458. }
  459. return pFrameLock->m_iLock;
  460. }
  461. //-----------------------------------------------------------------------------
  462. //
  463. //-----------------------------------------------------------------------------
  464. int *CDataCacheSection::GetFrameUnlockCounterPtr()
  465. {
  466. return &m_nFrameUnlockCounter;
  467. }
  468. //-----------------------------------------------------------------------------
  469. // Purpose: Lock management, not for the feint of heart
  470. //-----------------------------------------------------------------------------
  471. int CDataCacheSection::GetLockCount( DataCacheHandle_t handle )
  472. {
  473. return m_LRU.LockCount( (memhandle_t)handle );
  474. }
  475. //-----------------------------------------------------------------------------
  476. //
  477. //-----------------------------------------------------------------------------
  478. int CDataCacheSection::BreakLock( DataCacheHandle_t handle )
  479. {
  480. return m_LRU.BreakLock( (memhandle_t)handle );
  481. }
  482. //-----------------------------------------------------------------------------
  483. // Purpose: Explicitly mark an item as "recently used"
  484. //-----------------------------------------------------------------------------
  485. bool CDataCacheSection::Touch( DataCacheHandle_t handle )
  486. {
  487. m_LRU.TouchResource( (memhandle_t)handle );
  488. return true;
  489. }
  490. //-----------------------------------------------------------------------------
  491. // Purpose: Explicitly mark an item as "least recently used".
  492. //-----------------------------------------------------------------------------
  493. bool CDataCacheSection::Age( DataCacheHandle_t handle )
  494. {
  495. m_LRU.MarkAsStale( (memhandle_t)handle );
  496. return true;
  497. }
  498. //-----------------------------------------------------------------------------
  499. // Purpose: Empty the cache. Returns bytes released, will remove locked items if force specified
  500. //-----------------------------------------------------------------------------
  501. unsigned CDataCacheSection::Flush( bool bUnlockedOnly, bool bNotify )
  502. {
  503. VPROF( "CDataCacheSection::Flush" );
  504. AUTO_LOCK( m_mutex );
  505. DataCacheNotificationType_t notificationType = ( bNotify )? DC_FLUSH_DISCARD : DC_NONE;
  506. memhandle_t hCurrent;
  507. memhandle_t hNext;
  508. unsigned nBytesFlushed = 0;
  509. unsigned nBytesCurrent = 0;
  510. hCurrent = GetFirstUnlockedItem();
  511. while ( hCurrent != INVALID_MEMHANDLE )
  512. {
  513. hNext = GetNextItem( hCurrent );
  514. nBytesCurrent = AccessItem( hCurrent )->size;
  515. if ( DiscardItem( hCurrent, notificationType ) )
  516. {
  517. nBytesFlushed += nBytesCurrent;
  518. }
  519. hCurrent = hNext;
  520. }
  521. if ( !bUnlockedOnly )
  522. {
  523. hCurrent = GetFirstLockedItem();
  524. while ( hCurrent != INVALID_MEMHANDLE )
  525. {
  526. hNext = GetNextItem( hCurrent );
  527. nBytesCurrent = AccessItem( hCurrent )->size;
  528. if ( DiscardItem( hCurrent, notificationType ) )
  529. {
  530. nBytesFlushed += nBytesCurrent;
  531. }
  532. hCurrent = hNext;
  533. }
  534. }
  535. return nBytesFlushed;
  536. }
  537. //-----------------------------------------------------------------------------
  538. // Purpose: Dump the oldest items to free the specified amount of memory. Returns amount actually freed
  539. //-----------------------------------------------------------------------------
  540. unsigned CDataCacheSection::Purge( unsigned nBytes )
  541. {
  542. VPROF( "CDataCacheSection::Purge" );
  543. AUTO_LOCK( m_mutex );
  544. unsigned nBytesPurged = 0;
  545. unsigned nBytesCurrent = 0;
  546. memhandle_t hCurrent = GetFirstUnlockedItem();
  547. memhandle_t hNext;
  548. while ( hCurrent != INVALID_MEMHANDLE && nBytes > 0 )
  549. {
  550. hNext = GetNextItem( hCurrent );
  551. nBytesCurrent = AccessItem( hCurrent )->size;
  552. if ( DiscardItem( hCurrent, DC_FLUSH_DISCARD ) )
  553. {
  554. nBytesPurged += nBytesCurrent;
  555. nBytes -= min( nBytesCurrent, nBytes );
  556. }
  557. hCurrent = hNext;
  558. }
  559. return nBytesPurged;
  560. }
  561. //-----------------------------------------------------------------------------
  562. // Purpose: Dump the oldest items to free the specified number of items. Returns number actually freed
  563. //-----------------------------------------------------------------------------
  564. unsigned CDataCacheSection::PurgeItems( unsigned nItems )
  565. {
  566. AUTO_LOCK( m_mutex );
  567. unsigned nPurged = 0;
  568. memhandle_t hCurrent = GetFirstUnlockedItem();
  569. memhandle_t hNext;
  570. while ( hCurrent != INVALID_MEMHANDLE && nItems )
  571. {
  572. hNext = GetNextItem( hCurrent );
  573. if ( DiscardItem( hCurrent, DC_FLUSH_DISCARD ) )
  574. {
  575. nItems--;
  576. nPurged++;
  577. }
  578. hCurrent = hNext;
  579. }
  580. return nPurged;
  581. }
  582. //-----------------------------------------------------------------------------
  583. // Purpose: Output the state of the section
  584. //-----------------------------------------------------------------------------
  585. void CDataCacheSection::OutputReport( DataCacheReportType_t reportType )
  586. {
  587. m_pSharedCache->OutputReport( reportType, GetName() );
  588. }
  589. //-----------------------------------------------------------------------------
  590. // Purpose: Updates the size of a specific item
  591. // Input : handle -
  592. // newSize -
  593. //-----------------------------------------------------------------------------
  594. void CDataCacheSection::UpdateSize( DataCacheHandle_t handle, unsigned int nNewSize )
  595. {
  596. DataCacheItem_t *pItem = m_LRU.LockResource( (memhandle_t)handle );
  597. if ( !pItem )
  598. {
  599. // If it's gone from memory, size is already irrelevant
  600. return;
  601. }
  602. unsigned oldSize = pItem->size;
  603. if ( oldSize != nNewSize )
  604. {
  605. // Update the size
  606. pItem->size = nNewSize;
  607. int bytesAdded = nNewSize - oldSize;
  608. // If change would grow cache size, then purge items until we have room
  609. if ( bytesAdded > 0 )
  610. {
  611. m_pSharedCache->EnsureCapacity( bytesAdded );
  612. }
  613. m_LRU.NotifySizeChanged( (memhandle_t)handle, oldSize, nNewSize );
  614. NoteSizeChanged( oldSize, nNewSize );
  615. }
  616. m_LRU.UnlockResource( (memhandle_t)handle );
  617. }
  618. //-----------------------------------------------------------------------------
  619. //
  620. //-----------------------------------------------------------------------------
  621. memhandle_t CDataCacheSection::GetFirstUnlockedItem()
  622. {
  623. memhandle_t hCurrent;
  624. hCurrent = m_LRU.GetFirstUnlocked();
  625. while ( hCurrent != INVALID_MEMHANDLE )
  626. {
  627. if ( AccessItem( hCurrent )->pSection == this )
  628. {
  629. return hCurrent;
  630. }
  631. hCurrent = m_LRU.GetNext( hCurrent );
  632. }
  633. return INVALID_MEMHANDLE;
  634. }
  635. memhandle_t CDataCacheSection::GetFirstLockedItem()
  636. {
  637. memhandle_t hCurrent;
  638. hCurrent = m_LRU.GetFirstLocked();
  639. while ( hCurrent != INVALID_MEMHANDLE )
  640. {
  641. if ( AccessItem( hCurrent )->pSection == this )
  642. {
  643. return hCurrent;
  644. }
  645. hCurrent = m_LRU.GetNext( hCurrent );
  646. }
  647. return INVALID_MEMHANDLE;
  648. }
  649. memhandle_t CDataCacheSection::GetNextItem( memhandle_t hCurrent )
  650. {
  651. hCurrent = m_LRU.GetNext( hCurrent );
  652. while ( hCurrent != INVALID_MEMHANDLE )
  653. {
  654. if ( AccessItem( hCurrent )->pSection == this )
  655. {
  656. return hCurrent;
  657. }
  658. hCurrent = m_LRU.GetNext( hCurrent );
  659. }
  660. return INVALID_MEMHANDLE;
  661. }
  662. bool CDataCacheSection::DiscardItem( memhandle_t hItem, DataCacheNotificationType_t type )
  663. {
  664. DataCacheItem_t *pItem = AccessItem( hItem );
  665. if ( DiscardItemData( pItem, type ) )
  666. {
  667. if ( m_LRU.LockCount( hItem ) )
  668. {
  669. m_LRU.BreakLock( hItem );
  670. NoteUnlock( pItem->size );
  671. }
  672. FrameLock_t *pFrameLock = m_ThreadFrameLock.Get();
  673. if ( pFrameLock )
  674. {
  675. int iThread = pFrameLock->m_iThread;
  676. if ( pItem->pNextFrameLocked[iThread] != DC_NO_NEXT_LOCKED )
  677. {
  678. if ( pFrameLock->m_pFirst == pItem )
  679. {
  680. pFrameLock->m_pFirst = pItem->pNextFrameLocked[iThread];
  681. }
  682. else
  683. {
  684. DataCacheItem_t *pCurrent = pFrameLock->m_pFirst;
  685. while ( pCurrent )
  686. {
  687. if ( pCurrent->pNextFrameLocked[iThread] == pItem )
  688. {
  689. pCurrent->pNextFrameLocked[iThread] = pItem->pNextFrameLocked[iThread];
  690. break;
  691. }
  692. pCurrent = pCurrent->pNextFrameLocked[iThread];
  693. }
  694. }
  695. pItem->pNextFrameLocked[iThread] = DC_NO_NEXT_LOCKED;
  696. }
  697. }
  698. #ifdef _DEBUG
  699. for ( int i = 0; i < DC_MAX_THREADS_FRAMELOCKED; i++ )
  700. {
  701. if ( pItem->pNextFrameLocked[i] != DC_NO_NEXT_LOCKED )
  702. {
  703. DebuggerBreak(); // higher level code needs to handle better
  704. }
  705. }
  706. #endif
  707. pItem->pSection = NULL; // inhibit callbacks from lower level resource system
  708. m_LRU.DestroyResource( hItem );
  709. return true;
  710. }
  711. return false;
  712. }
  713. bool CDataCacheSection::DiscardItemData( DataCacheItem_t *pItem, DataCacheNotificationType_t type )
  714. {
  715. if ( pItem )
  716. {
  717. if ( type != DC_NONE )
  718. {
  719. Assert( type == DC_AGE_DISCARD || type == DC_FLUSH_DISCARD || DC_REMOVED );
  720. if ( type == DC_AGE_DISCARD && m_pSharedCache->IsInFlush() )
  721. type = DC_FLUSH_DISCARD;
  722. DataCacheNotification_t notification =
  723. {
  724. type,
  725. GetName(),
  726. pItem->clientId,
  727. pItem->pItemData,
  728. pItem->size
  729. };
  730. bool bResult = m_pClient->HandleCacheNotification( notification );
  731. AssertMsg( bResult, "Refusal of cache drop not yet implemented!" );
  732. if ( bResult )
  733. {
  734. NoteRemove( pItem->size );
  735. }
  736. return bResult;
  737. }
  738. OnRemove( pItem->clientId );
  739. pItem->pSection = NULL;
  740. pItem->pItemData = NULL,
  741. pItem->clientId = 0;
  742. NoteRemove( pItem->size );
  743. return true;
  744. }
  745. return false;
  746. }
  747. //-----------------------------------------------------------------------------
  748. // CDataCacheSectionFastFind
  749. //-----------------------------------------------------------------------------
  750. DataCacheHandle_t CDataCacheSectionFastFind::DoFind( DataCacheClientID_t clientId )
  751. {
  752. AUTO_LOCK( m_mutex );
  753. UtlHashFastHandle_t hHash = m_Handles.Find( Hash4( &clientId ) );
  754. if( hHash != m_Handles.InvalidHandle() )
  755. return m_Handles[hHash];
  756. return DC_INVALID_HANDLE;
  757. }
  758. void CDataCacheSectionFastFind::OnAdd( DataCacheClientID_t clientId, DataCacheHandle_t hCacheItem )
  759. {
  760. AUTO_LOCK( m_mutex );
  761. Assert( m_Handles.Find( Hash4( &clientId ) ) == m_Handles.InvalidHandle());
  762. m_Handles.FastInsert( Hash4( &clientId ), hCacheItem );
  763. }
  764. void CDataCacheSectionFastFind::OnRemove( DataCacheClientID_t clientId )
  765. {
  766. AUTO_LOCK( m_mutex );
  767. UtlHashFastHandle_t hHash = m_Handles.Find( Hash4( &clientId ) );
  768. Assert( hHash != m_Handles.InvalidHandle());
  769. if( hHash != m_Handles.InvalidHandle() )
  770. return m_Handles.Remove( hHash );
  771. }
  772. //-----------------------------------------------------------------------------
  773. // CDataCache
  774. //-----------------------------------------------------------------------------
  775. //-----------------------------------------------------------------------------
  776. // Convar callback to change data cache
  777. //-----------------------------------------------------------------------------
  778. void DataCacheSize_f( IConVar *pConVar, const char *pOldString, float flOldValue )
  779. {
  780. ConVarRef var( pConVar );
  781. int nOldValue = (int)flOldValue;
  782. if ( var.GetInt() != nOldValue )
  783. {
  784. g_DataCache.SetSize( var.GetInt() * 1024 * 1024 );
  785. }
  786. }
  787. ConVar datacachesize( "datacachesize", "64", FCVAR_INTERNAL_USE, "Size in MB.", true, 32, true, 512, DataCacheSize_f );
  788. //-----------------------------------------------------------------------------
  789. // Connect, disconnect
  790. //-----------------------------------------------------------------------------
  791. bool CDataCache::Connect( CreateInterfaceFn factory )
  792. {
  793. if ( !BaseClass::Connect( factory ) )
  794. return false;
  795. g_DataCache.SetSize( datacachesize.GetInt() * 1024 * 1024 );
  796. g_pDataCache = this;
  797. return true;
  798. }
  799. void CDataCache::Disconnect()
  800. {
  801. g_pDataCache = NULL;
  802. BaseClass::Disconnect();
  803. }
  804. //-----------------------------------------------------------------------------
  805. // Init, Shutdown
  806. //-----------------------------------------------------------------------------
  807. InitReturnVal_t CDataCache::Init( void )
  808. {
  809. return BaseClass::Init();
  810. }
  811. void CDataCache::Shutdown( void )
  812. {
  813. Flush( false, false );
  814. BaseClass::Shutdown();
  815. }
  816. //-----------------------------------------------------------------------------
  817. // Query interface
  818. //-----------------------------------------------------------------------------
  819. void *CDataCache::QueryInterface( const char *pInterfaceName )
  820. {
  821. // Loading the datacache DLL mounts *all* interfaces
  822. // This includes the backward-compatible interfaces + IStudioDataCache
  823. CreateInterfaceFn factory = Sys_GetFactoryThis(); // This silly construction is necessary
  824. return factory( pInterfaceName, NULL ); // to prevent the LTCG compiler from crashing.
  825. }
  826. //-----------------------------------------------------------------------------
  827. //
  828. //-----------------------------------------------------------------------------
  829. CDataCache::CDataCache()
  830. : m_mutex( m_LRU.AccessMutex() )
  831. {
  832. memset( &m_status, 0, sizeof(m_status) );
  833. m_bInFlush = false;
  834. }
  835. //-----------------------------------------------------------------------------
  836. // Purpose: Controls cache size.
  837. //-----------------------------------------------------------------------------
  838. void CDataCache::SetSize( int nMaxBytes )
  839. {
  840. m_LRU.SetTargetSize( nMaxBytes );
  841. m_LRU.FlushToTargetSize();
  842. nMaxBytes /= 1024 * 1024;
  843. if ( datacachesize.GetInt() != nMaxBytes )
  844. {
  845. datacachesize.SetValue( nMaxBytes );
  846. }
  847. }
  848. //-----------------------------------------------------------------------------
  849. // Purpose: Controls cache options.
  850. //-----------------------------------------------------------------------------
  851. void CDataCache::SetOptions( unsigned options )
  852. {
  853. for ( int i = 0; m_Sections.Count(); i++ )
  854. {
  855. m_Sections[i]->SetOptions( options );
  856. }
  857. }
  858. //-----------------------------------------------------------------------------
  859. // Purpose: Controls cache section size.
  860. //-----------------------------------------------------------------------------
  861. void CDataCache::SetSectionLimits( const char *pszSectionName, const DataCacheLimits_t &limits )
  862. {
  863. IDataCacheSection *pSection = FindSection( pszSectionName );
  864. if ( !pSection )
  865. {
  866. DevMsg( "Cannot find requested cache section \"%s\"", pszSectionName );
  867. return;
  868. }
  869. pSection->SetLimits( limits );
  870. }
  871. //-----------------------------------------------------------------------------
  872. // Purpose: Get the current state of the cache
  873. //-----------------------------------------------------------------------------
  874. void CDataCache::GetStatus( DataCacheStatus_t *pStatus, DataCacheLimits_t *pLimits )
  875. {
  876. if ( pStatus )
  877. {
  878. *pStatus = m_status;
  879. }
  880. if ( pLimits )
  881. {
  882. Construct( pLimits );
  883. pLimits->nMaxBytes = m_LRU.TargetSize();
  884. }
  885. }
  886. //-----------------------------------------------------------------------------
  887. // Purpose: Add a section to the cache
  888. //-----------------------------------------------------------------------------
  889. IDataCacheSection *CDataCache::AddSection( IDataCacheClient *pClient, const char *pszSectionName, const DataCacheLimits_t &limits, bool bSupportFastFind )
  890. {
  891. CDataCacheSection *pSection;
  892. pSection = (CDataCacheSection *)FindSection( pszSectionName );
  893. if ( pSection )
  894. {
  895. AssertMsg1( pSection->GetClient() == pClient, "Duplicate cache section name \"%s\"", pszSectionName );
  896. return pSection;
  897. }
  898. if ( !bSupportFastFind )
  899. pSection = new CDataCacheSection( this, pClient, pszSectionName );
  900. else
  901. pSection = new CDataCacheSectionFastFind( this, pClient, pszSectionName );
  902. pSection->SetLimits( limits );
  903. m_Sections.AddToTail( pSection );
  904. return pSection;
  905. }
  906. //-----------------------------------------------------------------------------
  907. // Purpose: Remove a section from the cache
  908. //-----------------------------------------------------------------------------
  909. void CDataCache::RemoveSection( const char *pszClientName, bool bCallFlush )
  910. {
  911. int iSection = FindSectionIndex( pszClientName );
  912. if ( iSection != m_Sections.InvalidIndex() )
  913. {
  914. if ( bCallFlush )
  915. {
  916. m_Sections[iSection]->Flush( false );
  917. }
  918. delete m_Sections[iSection];
  919. m_Sections.FastRemove( iSection );
  920. return;
  921. }
  922. }
  923. //-----------------------------------------------------------------------------
  924. // Purpose: Find a section of the cache
  925. //-----------------------------------------------------------------------------
  926. IDataCacheSection *CDataCache::FindSection( const char *pszClientName )
  927. {
  928. int iSection = FindSectionIndex( pszClientName );
  929. if ( iSection != m_Sections.InvalidIndex() )
  930. {
  931. return m_Sections[iSection];
  932. }
  933. return NULL;
  934. }
  935. //-----------------------------------------------------------------------------
  936. //
  937. //-----------------------------------------------------------------------------
  938. void CDataCache::EnsureCapacity( unsigned nBytes )
  939. {
  940. VPROF( "CDataCache::EnsureCapacity" );
  941. m_LRU.EnsureCapacity( nBytes );
  942. }
  943. //-----------------------------------------------------------------------------
  944. // Purpose: Dump the oldest items to free the specified amount of memory. Returns amount actually freed
  945. //-----------------------------------------------------------------------------
  946. unsigned CDataCache::Purge( unsigned nBytes )
  947. {
  948. VPROF( "CDataCache::Purge" );
  949. return m_LRU.Purge( nBytes );
  950. }
  951. //-----------------------------------------------------------------------------
  952. // Purpose: Empty the cache. Returns bytes released, will remove locked items if force specified
  953. //-----------------------------------------------------------------------------
  954. unsigned CDataCache::Flush( bool bUnlockedOnly, bool bNotify )
  955. {
  956. VPROF( "CDataCache::Flush" );
  957. unsigned result;
  958. if ( m_bInFlush )
  959. {
  960. return 0;
  961. }
  962. m_bInFlush = true;
  963. if ( bUnlockedOnly )
  964. {
  965. result = m_LRU.FlushAllUnlocked();
  966. }
  967. else
  968. {
  969. result = m_LRU.FlushAll();
  970. }
  971. m_bInFlush = false;
  972. return result;
  973. }
  974. //-----------------------------------------------------------------------------
  975. // Purpose: Output the state of the cache
  976. //-----------------------------------------------------------------------------
  977. void CDataCache::OutputReport( DataCacheReportType_t reportType, const char *pszSection )
  978. {
  979. int i;
  980. AUTO_LOCK( m_mutex );
  981. int bytesUsed = m_LRU.UsedSize();
  982. int bytesTotal = m_LRU.TargetSize();
  983. float percent = 100.0f * (float)bytesUsed / (float)bytesTotal;
  984. CUtlVector<memhandle_t> lruList, lockedlist;
  985. m_LRU.GetLockHandleList( lockedlist );
  986. m_LRU.GetLRUHandleList( lruList );
  987. CDataCacheSection *pSection = NULL;
  988. if ( pszSection )
  989. {
  990. pSection = (CDataCacheSection *)FindSection( pszSection );
  991. if ( !pSection )
  992. {
  993. Msg( "Unknown cache section %s\n", pszSection );
  994. return;
  995. }
  996. }
  997. if ( reportType == DC_DETAIL_REPORT )
  998. {
  999. CUtlRBTree< memhandle_t, int > sortedbysize( 0, 0, SortMemhandlesBySizeLessFunc );
  1000. for ( i = 0; i < lockedlist.Count(); ++i )
  1001. {
  1002. if ( !pSection || AccessItem( lockedlist[ i ] )->pSection == pSection )
  1003. sortedbysize.Insert( lockedlist[ i ] );
  1004. }
  1005. for ( i = 0; i < lruList.Count(); ++i )
  1006. {
  1007. if ( !pSection || AccessItem( lruList[ i ] )->pSection == pSection )
  1008. sortedbysize.Insert( lruList[ i ] );
  1009. }
  1010. for ( i = sortedbysize.FirstInorder(); i != sortedbysize.InvalidIndex(); i = sortedbysize.NextInorder( i ) )
  1011. {
  1012. OutputItemReport( sortedbysize[ i ] );
  1013. }
  1014. OutputReport( DC_SUMMARY_REPORT, pszSection );
  1015. }
  1016. else if ( reportType == DC_DETAIL_REPORT_LRU )
  1017. {
  1018. for ( i = 0; i < lockedlist.Count(); ++i )
  1019. {
  1020. if ( !pSection || AccessItem( lockedlist[ i ] )->pSection == pSection )
  1021. OutputItemReport( lockedlist[ i ] );
  1022. }
  1023. for ( i = 0; i < lruList.Count(); ++i )
  1024. {
  1025. if ( !pSection || AccessItem( lruList[ i ] )->pSection == pSection )
  1026. OutputItemReport( lruList[ i ] );
  1027. }
  1028. OutputReport( DC_SUMMARY_REPORT, pszSection );
  1029. }
  1030. else if ( reportType == DC_SUMMARY_REPORT )
  1031. {
  1032. if ( !pszSection )
  1033. {
  1034. // summary for all of the sections
  1035. for ( int i = 0; i < m_Sections.Count(); ++i )
  1036. {
  1037. if ( m_Sections[i]->GetName() )
  1038. {
  1039. OutputReport( DC_SUMMARY_REPORT, m_Sections[i]->GetName() );
  1040. }
  1041. }
  1042. Msg( "Summary: %i resources total %s, %.2f %% of capacity\n", lockedlist.Count() + lruList.Count(), Q_pretifymem( bytesUsed, 2, true ), percent );
  1043. }
  1044. else
  1045. {
  1046. // summary for the specified section
  1047. DataCacheItem_t *pItem;
  1048. int sectionBytes = 0;
  1049. int sectionCount = 0;
  1050. for ( i = 0; i < lockedlist.Count(); ++i )
  1051. {
  1052. if ( AccessItem( lockedlist[ i ] )->pSection == pSection )
  1053. {
  1054. pItem = g_DataCache.m_LRU.GetResource_NoLockNoLRUTouch( lockedlist[i] );
  1055. sectionBytes += pItem->size;
  1056. sectionCount++;
  1057. }
  1058. }
  1059. for ( i = 0; i < lruList.Count(); ++i )
  1060. {
  1061. if ( AccessItem( lruList[ i ] )->pSection == pSection )
  1062. {
  1063. pItem = g_DataCache.m_LRU.GetResource_NoLockNoLRUTouch( lruList[i] );
  1064. sectionBytes += pItem->size;
  1065. sectionCount++;
  1066. }
  1067. }
  1068. int sectionSize = 1;
  1069. float sectionPercent;
  1070. if ( pSection->GetLimits().nMaxBytes == (unsigned int)-1 )
  1071. {
  1072. // section unrestricted, base on total size
  1073. sectionSize = bytesTotal;
  1074. }
  1075. else if ( pSection->GetLimits().nMaxBytes )
  1076. {
  1077. sectionSize = pSection->GetLimits().nMaxBytes;
  1078. }
  1079. sectionPercent = 100.0f * (float)sectionBytes/(float)sectionSize;
  1080. Msg( "Section [%s]: %i resources total %s, %.2f %% of limit (%s)\n", pszSection, sectionCount, Q_pretifymem( sectionBytes, 2, true ), sectionPercent, Q_pretifymem( sectionSize, 2, true ) );
  1081. }
  1082. }
  1083. }
  1084. //-------------------------------------
  1085. void CDataCache::OutputItemReport( memhandle_t hItem )
  1086. {
  1087. AUTO_LOCK( m_mutex );
  1088. DataCacheItem_t *pItem = m_LRU.GetResource_NoLockNoLRUTouch( hItem );
  1089. if ( !pItem )
  1090. return;
  1091. CDataCacheSection *pSection = pItem->pSection;
  1092. char name[DC_MAX_ITEM_NAME+1];
  1093. name[0] = 0;
  1094. pSection->GetClient()->GetItemName( pItem->clientId, pItem->pItemData, name, DC_MAX_ITEM_NAME );
  1095. Msg( "\t%16.16s : %12s : 0x%08x, 0x%p, 0x%p : %s : %s\n",
  1096. Q_pretifymem( pItem->size, 2, true ),
  1097. pSection->GetName(),
  1098. pItem->clientId, pItem->pItemData, hItem,
  1099. ( name[0] ) ? name : "unknown",
  1100. ( m_LRU.LockCount( hItem ) ) ? CFmtStr( "Locked %d", m_LRU.LockCount( hItem ) ).operator const char*() : "" );
  1101. }
  1102. //-----------------------------------------------------------------------------
  1103. //
  1104. //-----------------------------------------------------------------------------
  1105. int CDataCache::FindSectionIndex( const char *pszSection )
  1106. {
  1107. for ( int i = 0; i < m_Sections.Count(); i++ )
  1108. {
  1109. if ( stricmp( m_Sections[i]->GetName(), pszSection ) == 0 )
  1110. return i;
  1111. }
  1112. return m_Sections.InvalidIndex();
  1113. }
  1114. //-----------------------------------------------------------------------------
  1115. // Sorting utility used by the data cache report
  1116. //-----------------------------------------------------------------------------
  1117. bool CDataCache::SortMemhandlesBySizeLessFunc( const memhandle_t& lhs, const memhandle_t& rhs )
  1118. {
  1119. DataCacheItem_t *pItem1 = g_DataCache.m_LRU.GetResource_NoLockNoLRUTouch( lhs );
  1120. DataCacheItem_t *pItem2 = g_DataCache.m_LRU.GetResource_NoLockNoLRUTouch( rhs );
  1121. Assert( pItem1 );
  1122. Assert( pItem2 );
  1123. return pItem1->size < pItem2->size;
  1124. }