csgo-2018-source/public/tier1/utltscache.h
2021-07-24 21:11:47 -07:00

183 lines
4.7 KiB
C++

//===== Copyright © 1996-2006, Valve Corporation, All rights reserved. ======//
//
// Purpose: thread safe cache template class
//
//===========================================================================//
// a thread-safe cache. Cache queries and updates may be performed on multiple threads at once in a
// lock-free fashion. You must call the "FrameUpdate" function while no one is querying against
// this cache.
#include "tier1/utlintrusivelist.h"
// in order to use this class, KEYTYPE must implement bool IsAcceptable( KEYTYPE const & ), and
// RECORDTYPE must implement CreateCacheData( KEYTYPE const & )
template< class RECORDTYPE, class KEYTYPE, class EXTRACREATEDATA, int nNumCacheLines, int nAssociativity = 4 > class CUtlTSCache
{
public:
class CCacheEntry : public CAlignedNewDelete<16>
{
public:
CCacheEntry *m_pNext;
CCacheEntry *m_pNextPending;
KEYTYPE m_Key;
RECORDTYPE m_Data;
};
~CUtlTSCache( void )
{
PerFrameUpdate(); // move pending to free
for( CCacheEntry *pNode = m_pFreeList; pNode; )
{
CCacheEntry *pNext = pNode->m_pNext;
if ( ! ( ( ( pNode >= m_pInitiallyAllocatedNodes ) &&
( pNode < m_pInitiallyAllocatedNodes + InitialAllocationSize() ) ) ) )
{
delete pNode;
}
pNode = pNext;
}
delete[] m_pInitiallyAllocatedNodes;
}
CUtlTSCache( void )
{
MEM_ALLOC_CREDIT_CLASS();
memset( m_pCache, 0, sizeof( m_pCache ) );
m_pInitiallyAllocatedNodes = new CCacheEntry[ InitialAllocationSize() ];
m_pFreeList = NULL;
for( int i = 0; i < InitialAllocationSize(); i++ )
{
IntrusiveList::AddToHead( m_pFreeList, m_pInitiallyAllocatedNodes + i );
}
m_pPendingFreeList = NULL;
}
void PerFrameUpdate( void )
{
for( CCacheEntry *pNode = m_pPendingFreeList; pNode; pNode = pNode->m_pNextPending )
{
IntrusiveList::AddToHead( m_pFreeList, pNode );
}
m_pPendingFreeList = NULL;
}
// Invalidate() is NOT thread-safe. If you need a thread-safe invalidate, you're going to need
// to do something like store a generation count in the key.
void Invalidate( void )
{
for( int i = 0; i < ARRAYSIZE( m_pCache ); i++ )
{
CCacheEntry *pNode = m_pCache[i];
if ( pNode )
{
IntrusiveList::AddToHead( m_pFreeList, pNode );
}
m_pCache[i] = NULL;
}
}
// lookup a value, maybe add one
RECORDTYPE *Lookup( KEYTYPE const &key, EXTRACREATEDATA const &extraCreateData )
{
// first perform our hash function
uint nHash = HashItem( key ) % nNumCacheLines;
CCacheEntry **pCacheLine = m_pCache + nHash * nAssociativity;
// now, see if we have an acceptable node
CCacheEntry *pOldValue[nAssociativity];
int nOffset = ( nAssociativity ) ? -1 : 0;
for( int i = 0; i < nAssociativity; i++ )
{
pOldValue[i] = pCacheLine[i];
if ( pOldValue[i] )
{
if ( key.IsAcceptable( pOldValue[i]->m_Key ) )
{
return &( pOldValue[i]->m_Data );
}
}
else
{
nOffset = i; // replace empty lines first
}
}
// no acceptable entry. We must generate and replace one. We will use a pseudo-random replacement scheme
if ( ( nAssociativity > 1 ) && ( nOffset == -1 ) )
{
nOffset = ( m_nLineCounter++ ) % nAssociativity;
}
// get a node
CCacheEntry *pNode = GetNode();
pNode->m_Key = key;
pNode->m_Data.CreateCacheData( key, extraCreateData );
// we will look for a place to insert this. It is possible that we will find no good place and will just ditch it.
for( int i = 0; i < nAssociativity; i++ )
{
// try to install it.
if ( ThreadInterlockedAssignPointerIf( ( void * volatile * ) ( pCacheLine + nOffset ), pNode, pOldValue[nOffset] ) )
{
// put the old one on the pending free list
if ( pOldValue[nOffset] )
{
IntrusiveList::AddToHeadByFieldTS( m_pPendingFreeList, pOldValue[nOffset], &CCacheEntry::m_pNextPending );
}
return &( pNode->m_Data ); // success!
}
nOffset++;
if ( nOffset == nAssociativity )
{
nOffset = 0; // wrap around
}
}
// we failed to install this node into the cache. we'll not bother
IntrusiveList::AddToHeadByFieldTS( m_pPendingFreeList, pNode, &CCacheEntry::m_pNextPending );
return &( pNode->m_Data );
}
protected:
int InitialAllocationSize( void )
{
return nNumCacheLines * nAssociativity;
}
CCacheEntry *GetNode( void )
{
CCacheEntry *pNode = IntrusiveList::RemoveHeadTS( m_pFreeList );
if ( ! pNode )
{
pNode = new CCacheEntry;
}
return pNode;
}
CInterlockedUInt m_nLineCounter; // for cycling through cache lines
CCacheEntry *m_pCache[nNumCacheLines * nAssociativity];
CCacheEntry *m_pFreeList; // nodes available for the cache
CCacheEntry *m_pPendingFreeList; // nodes to be moved to the free list at end of frame
CCacheEntry *m_pInitiallyAllocatedNodes;
};