diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /public/gcsdk/sdocache.h | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'public/gcsdk/sdocache.h')
| -rw-r--r-- | public/gcsdk/sdocache.h | 1190 |
1 files changed, 1190 insertions, 0 deletions
diff --git a/public/gcsdk/sdocache.h b/public/gcsdk/sdocache.h new file mode 100644 index 0000000..850aef3 --- /dev/null +++ b/public/gcsdk/sdocache.h @@ -0,0 +1,1190 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Serialized Digital Object caching and manipulation +// +//============================================================================= + +#ifndef SBOCACHE_H +#define SBOCACHE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier1/utlhashmaplarge.h" +#include "tier1/utlqueue.h" +#include "tier1/utlvector.h" +#include <algorithm> + +namespace GCSDK +{ +// Call to register SDOs. All SDO types must be registered before loaded +#define REG_SDO( classname ) GSDOCache().RegisterSDO( classname::k_eType, #classname ) + +// A string used to tell the difference between nil objects and actual objects in memcached +extern const char k_rgchNilObjSerializedValue[]; + + +//----------------------------------------------------------------------------- +// Purpose: Keeps a moving average of a data set +//----------------------------------------------------------------------------- +template< int SAMPLES > +class CMovingAverage +{ +public: + CMovingAverage() + { + Reset(); + } + + void Reset() + { + memset( m_rglSamples, 0, sizeof( m_rglSamples ) ); + m_cSamples = 0; + m_lTotal = 0; + } + + void AddSample( int64 lSample ) + { + int iIndex = m_cSamples % SAMPLES; + m_lTotal += ( lSample - m_rglSamples[iIndex] ); + m_rglSamples[iIndex] = lSample; + m_cSamples++; + } + + uint64 GetAveragedSample() const + { + if ( !m_cSamples ) + return 0; + + int64 iMax = (int64)MIN( m_cSamples, SAMPLES ); + return m_lTotal / iMax; + } + +private: + int64 m_rglSamples[SAMPLES]; + int64 m_lTotal; + uint64 m_cSamples; +}; + + +//----------------------------------------------------------------------------- +// Purpose: Global accessor to the manager +//----------------------------------------------------------------------------- +class CSDOCache; +CSDOCache &GSDOCache(); + + +//----------------------------------------------------------------------------- +// Purpose: interface to a Database Backed Object +//----------------------------------------------------------------------------- +class ISDO +{ +public: + virtual ~ISDO() {} + + // Identification + virtual int GetType() const = 0; + virtual uint32 GetHashCode() const = 0; + virtual bool IsEqual( const ISDO *pSDO ) const = 0; + + // Ref counting + virtual int AddRef() = 0; + virtual int Release() = 0; + virtual int GetRefCount() = 0; + + // memory usage + virtual size_t CubBytesUsed() = 0; + + // Serialization tools + virtual bool BReadFromBuffer( const byte *pubData, int cubData ) = 0; + virtual void WriteToBuffer( CUtlBuffer &memBuffer ) = 0; + + // memcached batching tools + virtual void GetMemcachedKeyName( CFmtStr &strDest ) = 0; + + // SQL loading + virtual bool BYldLoadFromSQL( CUtlVector<ISDO *> &vecSDOToLoad, CUtlVector<bool> &vecResults ) const = 0; + + // post-load initialization (whether loaded from SQL or memcached) + virtual void PostLoadInit() = 0; + + // comparison function for validating memcached copies vs SQL copies + virtual bool IsIdentical( ISDO *pSDO ) = 0; + + // Returns true if this is not the actual object, but a placeholder to remember that this object + // doesn't actually exist + virtual bool BNilObject() const = 0; + + // Allocs an SDO that must return true for IsEqual( this ) and BNilObject(). This is so we can + // cache load attempts for objects that don't exist + virtual ISDO *AllocNilObject() = 0; + + // Creates a key name that looks like "Prefix_%u" but faster +}; + + +//----------------------------------------------------------------------------- +// Purpose: base class for a Serialized Digital Object +//----------------------------------------------------------------------------- +template<typename KeyType, int eSDOType, class ProtoMsg> +class CBaseSDO : public ISDO +{ +public: + typedef KeyType KeyType_t; + enum { k_eType = eSDOType }; + + CBaseSDO( const KeyType &key ) : m_Key( key ), m_nRefCount( 0 ) {} + + const KeyType &GetKey() const { return m_Key; } + + // ISDO implementation + virtual int AddRef(); + virtual int Release(); + virtual int GetRefCount(); + virtual int GetType() const { return eSDOType; } + virtual bool BNilObject() const { return false; } + virtual uint32 GetHashCode() const; + virtual bool BReadFromBuffer( const byte *pubData, int cubData ); + virtual void WriteToBuffer( CUtlBuffer &memBuffer ); + + // Implement this in a subclass if there's some value like 0 or -1 that's invalid so + // the system can know to not even attempt to load it + static bool BKeyValid( const KeyType_t &key ) { return true; } + + // We use protobufs for all serialization + virtual void SerializeToProtobuf( ProtoMsg &msg ) = 0; + virtual bool DeserializeFromProtobuf( const ProtoMsg &msg ) = 0; + + // default comparison function - override to do your own compare + virtual bool IsEqual( const ISDO *pSDO ) const; + + // default load from SQL is no-op as not all types have permanent storage - override to create a + // batch load + virtual bool BYldLoadFromSQL( CUtlVector<ISDO *> &vecSDOToLoad, CUtlVector<bool> &vecResults ) const; + + // override to do initialization after load + virtual void PostLoadInit() {} + + // compares the serialized versions by default. Override to have more specific behavior + virtual bool IsIdentical( ISDO *pSDO ); + + // Creates a copy of the object with the same key + virtual ISDO *AllocNilObject(); + + // tools + bool WriteToMemcached(); + bool DeleteFromMemcached(); + + // Creates a key name that looks like "Prefix_%u" but faster. Also makes sure the correct buffer size is passed + template <size_t prefixBufSize> + void CreateSimpleMemcachedName( CFmtStr &strDest, char (&rgchPrefix)[prefixBufSize], uint32 unSuffix ) + { + CSDOCache::CreateSimpleMemcachedName( strDest, rgchPrefix, prefixBufSize - 1, unSuffix ); + } + +private: + int m_nRefCount; + KeyType m_Key; +}; + + +//----------------------------------------------------------------------------- +// Purpose: Represents an object we tried to load but didn't exist. We use +// this to cache the failure so we don't keep trying to look it up +//----------------------------------------------------------------------------- +template<typename KeyType, int eSDOType, class ProtoMsg> +class CNilSDO : public CBaseSDO<KeyType, eSDOType, ProtoMsg> +{ +public: + CNilSDO( const KeyType &key, const char *pchMemcachedKeyName ) : CBaseSDO<KeyType, eSDOType, ProtoMsg>( key ), m_sMemcachedKeyName( pchMemcachedKeyName ) {} + + virtual bool BNilObject() const { return true; } + virtual bool BReadFromBuffer( const byte *pubData, int cubData ) { return false; } + virtual void WriteToBuffer( CUtlBuffer &memBuffer ) { memBuffer.PutString( k_rgchNilObjSerializedValue ); } + virtual void SerializeToProtobuf( ProtoMsg &msg ) {} + virtual bool DeserializeFromProtobuf( const ProtoMsg &msg ) { return false; } + virtual size_t CubBytesUsed() { return sizeof( *this ) + m_sMemcachedKeyName.Length(); } + virtual void GetMemcachedKeyName( CFmtStr &strKey ) { V_strncpy( strKey.Access(), m_sMemcachedKeyName, FMTSTR_STD_LEN ); } +private: + CUtlString m_sMemcachedKeyName; +}; + + +//----------------------------------------------------------------------------- +// Purpose: references to a database-backed object +// maintains refcount of the object +//----------------------------------------------------------------------------- +template<class T> +class CSDORef +{ + T *m_pSDO; +public: + CSDORef() { m_pSDO = NULL; } + explicit CSDORef( CSDORef<T> &SDORef ) { m_pSDO = SDORef.Get(); if ( m_pSDO ) m_pSDO->AddRef(); } + explicit CSDORef( T *pSDO ) { m_pSDO = pSDO; if ( m_pSDO ) m_pSDO->AddRef(); } + ~CSDORef() { if ( m_pSDO ) m_pSDO->Release(); } + + T *Get() { return m_pSDO; } + const T *Get() const { return m_pSDO; } + + T *operator->() { return Get(); } + const T *operator->() const { return Get(); } + + operator const T *() const { return m_pSDO; } + operator const T *() { return m_pSDO; } + operator T *() { return m_pSDO; } + + CSDORef<T> &operator=( T *pSDO ) { if ( m_pSDO ) m_pSDO->Release(); m_pSDO = pSDO; if ( m_pSDO ) m_pSDO->AddRef(); return *this; } + + bool operator !() const { return Get() == NULL; } + + bool IsValid( void ) const { return Get() != NULL; } +}; + + +//----------------------------------------------------------------------------- +// Purpose: manages a cache of SDO objects +//----------------------------------------------------------------------------- +class CSDOCache +{ +public: + CSDOCache(); + ~CSDOCache(); + + // Call to register SDOs. All SDO types must be registered before loaded + void RegisterSDO( int nType, const char *pchName ); + + // A struct to hold stats for the system. This is generated code in Steam. It would be great to make + // it generated code here if we could bring Steam's operational stats system in the GC + struct StatsSDOCache_t + { + uint64 m_cItemsLRUd; + uint64 m_cBytesLRUd; + uint64 m_cItemsUnreferenced; + uint64 m_cBytesUnreferenced; + uint64 m_cItemsInCache; + uint64 m_cBytesInCacheEst; + uint64 m_cItemsQueuedToLoad; + uint64 m_cItemsLoadedFromMemcached; + uint64 m_cItemsLoadedFromSQL; + uint64 m_cItemsFailedLoadFromSQL; + uint64 m_cQueuedMemcachedRequests; + uint64 m_cQueuedSQLRequests; + uint64 m_nSQLBatchSizeAvgx100; + uint64 m_nMemcachedBatchSizeAvgx100; + uint64 m_cSQLRequestsRejectedTooBusy; + uint64 m_cMemcachedRequestsRejectedTooBusy; + uint64 m_cNilItemsLoadedFromMemcached; + uint64 m_cNilItemsLoadedFromSQL; + }; + + // loads a SDO, and assigns a reference to it + // returns false if the item couldn't be loaded, or timed out loading + template<class T> + bool BYldLoadSDO( CSDORef<T> *pPSDORef, const typename T::KeyType_t &key, bool *pbTimeoutLoading = NULL ); + + // gets access to a SDO, but only if it's currently loaded + template<class T> + bool BGetLoadedSDO( CSDORef<T> *pPSDORef, const typename T::KeyType_t &key, bool *pbFoundNil = NULL ); + + // starts loading a SDO you're going to reference soon with the above BYldLoadSDO() + // use this to batch up requests, hinting a set then getting reference to a set is significantly faster + template<class T> + void HintLoadSDO( const typename T::KeyType_t &key ); + // as above, but starts load a set + template<class T> + void HintLoadSDO( const CUtlVector<typename T::KeyType_t> &vecKeys ); + + // Clears a nil object if one exists for this key. + template<class T> + void RemoveNil( const typename T::KeyType_t &key ); + + // force a deletes a SDO from the cache - waits until the object is not referenced + template<class T> + bool BYldDeleteSDO( const typename T::KeyType_t &key, uint64 unMicrosecondsToWaitForUnreferenced ); + + // SDO refcount management + void OnSDOReferenced( ISDO *pSDO ); + void OnSDOReleased( ISDO *pSDO ); + + // writes a SDO to memcached immediately + bool WriteSDOToMemcached( ISDO *pSDO ); + // delete the SDO record from memcached + bool DeleteSDOFromMemcached( ISDO *pSDO ); + + // job results + void OnSDOLoadSuccess( int eSDO, int iRequestID, bool bNilObj, ISDO **ppSDO ); + void OnMemcachedSDOLoadFailure( int eSDO, int iRequestID ); + void OnSQLSDOLoadFailure( int eSDO, int iRequestID, bool bSQLLayerSucceeded ); + void OnMemcachedLoadJobComplete( JobID_t jobID ); + void OnSQLLoadJobComplete( int eSDO, JobID_t jobID ); + + // test access - deletes all unreferenced objects + void Flush(); + + // stats access + StatsSDOCache_t &GetStats() { return m_StatsSDOCache; } + int CubReferencedEst(); // number of bytes referenced in the cache + + // prints info about the class + void Dump(); + + static void CreateSimpleMemcachedName( CFmtStr &strDest, const char *pchPrefix, uint32 unPrefixLen, uint32 unSuffix ); + + // memcached verification - returns the number of mismatches +//**tempcomment** void YldVerifyMemcachedData( CreateSDOFunc_t pCreateSDOFunc, CUtlVector<uint32> &vecIDs, int *pcMatches, int *pcMismatches ); + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, const char *pchName ); +#endif + + // Functions that need to be in the frame loop + virtual bool BFrameFuncRunJobsUntilCompleted( CLimitTimer &limitTimer ); + virtual bool BFrameFuncRunMemcachedQueriesUntilCompleted( CLimitTimer &limitTimer ); + virtual bool BFrameFuncRunSQLQueriesUntilCompleted( CLimitTimer &limitTimer ); + +private: + // Custom comparator for our hash map + class CDefPISDOEquals + { + public: + CDefPISDOEquals() {} + CDefPISDOEquals( int i ) {} + inline bool operator()( const ISDO *lhs, const ISDO *rhs ) const { return ( lhs->IsEqual( rhs ) ); } + inline bool operator!() const { return false; } + }; + + class CPISDOHashFunctor + { + public: + uint32 operator()(const ISDO *pSDO ) const { return pSDO->GetHashCode(); } + }; + + template<class T> + int FindLoadedSDO( const typename T::KeyType_t &key ); + + template<class T> + int QueueLoad( const typename T::KeyType_t &key ); + int QueueMemcachedLoad( ISDO *pSDO ); + + // items already loaded - Maps the SDO to the LRU position + CUtlHashMapLarge<ISDO *, int, CDefPISDOEquals, CPISDOHashFunctor> m_mapISDOLoaded; + + // items we have queued to load, in the state of either being loaded from memcached or SQL + // maps SDO to a list of jobs waiting on the load + CUtlHashMapLarge<ISDO *, CCopyableUtlVector<JobID_t>, CDefPISDOEquals, CPISDOHashFunctor> m_mapQueuedRequests; + + // requests to load from memcached + CUtlLinkedList<int, int> m_queueMemcachedRequests; + + // Jobs currently processing memcached load requests + CUtlVector<JobID_t> m_vecMemcachedJobs; + + // Loading from SQL is divided by SDO type + struct SQLRequestManager_t + { + // requests to load from SQL. Maps to an ID in the map of queued requests + CUtlLinkedList<int, int> m_queueRequestIDsToLoadFromSQL; + + // SQL jobs we have active doing reads for cache items + CUtlVector<JobID_t> m_vecSQLJobs; + }; + + // a queue of requests to load from SQL for each type + CUtlHashMapLarge<int, SQLRequestManager_t *> m_mapQueueSQLRequests; + + // jobs to wake up, since we've satisfied their SDO load request + struct JobToWake_t + { + JobID_t m_jobID; + bool m_bLoadLayerSuccess; + }; + CUtlLinkedList<JobToWake_t, int> m_queueJobsToContinue; + + struct LRUItem_t + { + ISDO * m_pSDO; + size_t m_cub; + }; + CUtlLinkedList<LRUItem_t, int> m_listLRU; + uint32 m_cubLRUItems; + void RemoveSDOFromLRU( int iMapSDOLoaded ); + + struct TypeStats_t + { + TypeStats_t() + : m_nLoaded( 0 ) + , m_nRefed( 0 ) + , m_cubUnrefed( 0 ) + , m_nNilObjects( 0 ) + {} + + CUtlString m_strName; + int m_nLoaded; + int m_nRefed; + int m_cubUnrefed; + int m_nNilObjects; + }; + + StatsSDOCache_t m_StatsSDOCache; + CMovingAverage<100> m_StatMemcachedBatchSize, m_StatSQLBatchSize; + CUtlMap<int, TypeStats_t> m_mapTypeStats; +}; + + +//----------------------------------------------------------------------------- +// Definition of CBaseSDO template functions now that CSDOCache is defined and +// GSDOCache() can safely be used. +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Purpose: adds a reference +//----------------------------------------------------------------------------- +template<typename KeyType, int ESDOType, class ProtoMsg> +int CBaseSDO<KeyType,ESDOType,ProtoMsg>::AddRef() +{ + if ( ++m_nRefCount == 1 ) + GSDOCache().OnSDOReferenced( this ); + + return m_nRefCount; +} + + +//----------------------------------------------------------------------------- +// Purpose: releases a reference +//----------------------------------------------------------------------------- +template<typename KeyType, int ESDOType, class ProtoMsg> +int CBaseSDO<KeyType,ESDOType,ProtoMsg>::Release() +{ + DbgVerify( m_nRefCount > 0 ); + + int nRefCount = --m_nRefCount; + + if ( nRefCount == 0 ) + GSDOCache().OnSDOReleased( this ); + + return nRefCount; +} + + +//----------------------------------------------------------------------------- +// Purpose: ref count +//----------------------------------------------------------------------------- +template<typename KeyType, int ESDOType, class ProtoMsg> +int CBaseSDO<KeyType,ESDOType,ProtoMsg>::GetRefCount() +{ + return m_nRefCount; +} + + +//----------------------------------------------------------------------------- +// Purpose: Hashes the object for insertion into a hashtable +//----------------------------------------------------------------------------- +template<typename KeyType, int ESDOType, class ProtoMsg> +uint32 CBaseSDO<KeyType,ESDOType,ProtoMsg>::GetHashCode() const +{ +#pragma pack( push, 1 ) + struct hashcode_t + { + int m_Type; + KeyType_t m_Key; + } hashStruct = { ESDOType, m_Key }; +#pragma pack( pop ) + + return PearsonsHashFunctor<hashcode_t>()( hashStruct ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Deserializes the object +//----------------------------------------------------------------------------- +template<typename KeyType, int ESDOType, class ProtoMsg> +bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::BReadFromBuffer( const byte *pubData, int cubData ) +{ + ProtoMsg msg; + if ( !msg.ParseFromArray( pubData, cubData ) ) + return false; + + if ( !DeserializeFromProtobuf( msg ) ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Serializes the object +//----------------------------------------------------------------------------- +template<typename KeyType, int ESDOType, class ProtoMsg> +void CBaseSDO<KeyType,ESDOType,ProtoMsg>::WriteToBuffer( CUtlBuffer &memBuffer ) +{ + ProtoMsg *pMsg = CProtoBufMsg<ProtoMsg>::AllocProto(); + + SerializeToProtobuf( *pMsg ); + uint32 unSize = pMsg->ByteSize(); + memBuffer.EnsureCapacity( memBuffer.Size() + unSize ); + pMsg->SerializeWithCachedSizesToArray( (uint8*)memBuffer.Base() + memBuffer.TellPut() ); + memBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, memBuffer.TellPut() + unSize ); + + CProtoBufMsg<ProtoMsg>::FreeProto( pMsg ); +} + + +//----------------------------------------------------------------------------- +// Purpose: does an immediate write of the object to memcached +//----------------------------------------------------------------------------- +template<typename KeyType, int ESDOType, class ProtoMsg> +bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::WriteToMemcached() +{ + return GSDOCache().WriteSDOToMemcached( this ); +} + + +//----------------------------------------------------------------------------- +// Purpose: does an immediate write of the object to memcached +//----------------------------------------------------------------------------- +template<typename KeyType, int ESDOType, class ProtoMsg> +bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::DeleteFromMemcached() +{ + return GSDOCache().DeleteSDOFromMemcached( this ); +} + + +//----------------------------------------------------------------------------- +// Purpose: default equality function - compares type and key +//----------------------------------------------------------------------------- +template<typename KeyType, int ESDOType, class ProtoMsg> +bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::IsEqual( const ISDO *pSDO ) const +{ + if ( GetType() != pSDO->GetType() ) + return false; + + return ( GetKey() == static_cast<const CBaseSDO<KeyType,ESDOType,ProtoMsg> *>( pSDO )->GetKey() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Batch load a group of SDO's of the same type from SQL. +// Default is no-op as not all types have permanent storage. +//----------------------------------------------------------------------------- +template<typename KeyType, int ESDOType, class ProtoMsg> +bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::BYldLoadFromSQL( CUtlVector<ISDO *> &vecSDOToLoad, CUtlVector<bool> &vecResults ) const +{ + FOR_EACH_VEC( vecResults, i ) + { + vecResults[i] = true; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: default validation function - compares serialized versions +//----------------------------------------------------------------------------- +bool CompareSDOObjects( ISDO *pSDO1, ISDO *pSDO2 ); + +template<typename KeyType, int ESDOType, class ProtoMsg> +bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::IsIdentical( ISDO *pSDO ) +{ + return CompareSDOObjects( this, pSDO ); +} + + +//----------------------------------------------------------------------------- +// Purpose: default validation function - compares serialized versions +//----------------------------------------------------------------------------- +template<typename KeyType, int ESDOType, class ProtoMsg> +ISDO *CBaseSDO<KeyType,ESDOType,ProtoMsg>::AllocNilObject() +{ + CFmtStr strKey; + GetMemcachedKeyName( strKey ); + return new CNilSDO<KeyType,ESDOType,ProtoMsg>( GetKey(), strKey ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Finds a loaded SDO in memory. Returns the index of the object +// into the loaded SDOs map +//----------------------------------------------------------------------------- +template<class T> +int CSDOCache::FindLoadedSDO( const typename T::KeyType_t &key ) +{ + // see if we have it in cache first + T probe( key ); + return m_mapISDOLoaded.Find( &probe ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Queues loading an SDO. Returns the index of the entry in the +// load queue +//----------------------------------------------------------------------------- +template<class T> +int CSDOCache::QueueLoad( const typename T::KeyType_t &key ) +{ + T probe( key ); + int iMap = m_mapQueuedRequests.Find( &probe ); + if ( m_mapQueuedRequests.IsValidIndex( iMap ) ) + return iMap; + + return QueueMemcachedLoad( new T( key ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Preloads the object into the local cache +//----------------------------------------------------------------------------- +template<class T> +void CSDOCache::HintLoadSDO( const typename T::KeyType_t &key ) +{ + // see if this is something we should even try to load + if ( !T::BKeyValid( key ) ) + return; + + // see if we have it in cache first + if ( !m_mapISDOLoaded.IsValidIndex( FindLoadedSDO<T>( key ) ) ) + { + QueueLoad<T>( key ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Preloads a set set of objects into the local cache +//----------------------------------------------------------------------------- +template<class T> +void CSDOCache::HintLoadSDO( const CUtlVector<typename T::KeyType_t> &vecKeys ) +{ + FOR_EACH_VEC( vecKeys, i ) + { + HintLoadSDO<T>( vecKeys[i] ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns an already-loaded SDO +//----------------------------------------------------------------------------- +template<class T> +bool CSDOCache::BGetLoadedSDO( CSDORef<T> *pPSDORef, const typename T::KeyType_t &key, bool *pbFoundNil ) +{ + if ( NULL != pbFoundNil ) + { + *pbFoundNil = false; + } + + int iMap = FindLoadedSDO<T>( key ); + if ( !m_mapISDOLoaded.IsValidIndex( iMap ) ) + return false; + + ISDO *pObj = m_mapISDOLoaded.Key( iMap ); + if ( pObj->BNilObject() ) + { + int iLRU = m_mapISDOLoaded[ iMap ]; + Assert( m_listLRU.IsValidIndex( iLRU ) ); + if ( m_listLRU.IsValidIndex( iLRU ) ) + { + // Even though we don't return nil objects, this is a hit on it + // so it needs to go to the back of the LRU + m_listLRU.LinkToTail( m_mapISDOLoaded[ iMap ] ); + } + + if ( NULL != pbFoundNil ) + { + *pbFoundNil = true; + } + + return false; + } + else + { + *pPSDORef = assert_cast<T*>( pObj ); + return true; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Loads the object into memory +//----------------------------------------------------------------------------- +template<class T> +bool CSDOCache::BYldLoadSDO( CSDORef<T> *pPSDORef, const typename T::KeyType_t &key, bool *pbTimeoutLoading /* = NULL */ ) +{ + VPROF_BUDGET( "CSDOCache::BYldLoadSDO", VPROF_BUDGETGROUP_STEAM ); + if ( pbTimeoutLoading ) + *pbTimeoutLoading = false; + + // Clear the current object the ref is holding + *pPSDORef = NULL; + + // see if this is something we should even try to load + if ( !T::BKeyValid( key ) ) + return false; + + // see if we have it in cache first + bool bFoundNil = false; + if ( BGetLoadedSDO( pPSDORef, key, &bFoundNil ) ) + return true; + + // If we've already tried to look this up in the past don't bother looking it up again + if ( bFoundNil ) + return false; + + // otherwise batch it for load + int iMap = QueueLoad<T>( key ); + + // make sure we could queue it + if ( !m_mapQueuedRequests.IsValidIndex( iMap ) ) + return false; + + // add the current job to this list waiting for the object to load + m_mapQueuedRequests[iMap].AddToTail( GJobCur().GetJobID() ); + + // wait for it to load (loader will signal our job when done) + if ( !GJobCur().BYieldingWaitForWorkItem() ) + { + if ( pbTimeoutLoading ) + *pbTimeoutLoading = true; + return false; + } + + // should be loaded - look up in the load map and try again + bool bRet = BGetLoadedSDO( pPSDORef, key ); + //Assert( bRet ); + + return bRet; +} + + +//----------------------------------------------------------------------------- +// Clears a nil object if one exists for this key. +//----------------------------------------------------------------------------- +template<class T> +void CSDOCache::RemoveNil( const typename T::KeyType_t &key ) +{ + // See if this key is allowed to exist + if ( !T::BKeyValid( key ) ) + { + AssertMsg( false, "RemoveNil called with an invalid key" ); + return; + } + + // Remove the nil if there is one + int iMap = FindLoadedSDO< T >( key ); + if ( m_mapISDOLoaded.IsValidIndex( iMap ) ) + { + ISDO *pSDO = m_mapISDOLoaded.Key( iMap ); + if ( !pSDO->BNilObject() ) + return; + + int iMapStats = m_mapTypeStats.Find( pSDO->GetType() ); + if ( m_mapTypeStats.IsValidIndex( iMapStats ) ) + { + m_mapTypeStats[iMapStats].m_nNilObjects--; + } + + RemoveSDOFromLRU( iMap ); + m_mapISDOLoaded.RemoveAt( iMap ); + delete pSDO; + } + + // Remove the object from memcached. Do this here because while we have for sure removed any nil that was in memory + // this may have been a nil that was not in memory but instead cached in memcached. + T temp( key ); + DeleteSDOFromMemcached( &temp ); +} + + +//----------------------------------------------------------------------------- +// Purpose: reloads an existing element from the SQL DB +//----------------------------------------------------------------------------- +template<class T> +bool CSDOCache::BYldDeleteSDO( const typename T::KeyType_t &key, uint64 unMicrosecondsToWaitForUnreferenced ) +{ + // see if we have it in cache first + int iMap = FindLoadedSDO<T>( key ); + if ( !m_mapISDOLoaded.IsValidIndex( iMap ) ) + { + T temp( key ); + temp.DeleteFromMemcached(); + return true; /* we're good, it's not loaded */ + } + + assert_cast<T *>(m_mapISDOLoaded.Key( iMap ))->DeleteFromMemcached(); + + // check the ref count + int64 cAttempts = MAX( 1LL, (int64)(unMicrosecondsToWaitForUnreferenced / k_cMicroSecPerShellFrame) ); + while ( cAttempts-- > 0 ) + { + if ( 0 == m_mapISDOLoaded.Key( iMap )->GetRefCount() ) + { + // delete the object + Assert( m_listLRU.IsValidIndex( m_mapISDOLoaded[iMap] ) ); + + RemoveSDOFromLRU( iMap ); + ISDO *pSDO = m_mapISDOLoaded.Key( iMap ); + m_mapISDOLoaded.RemoveAt( iMap ); + + int iMapStats = m_mapTypeStats.Find( m_mapISDOLoaded.Key( iMap )->GetType() ); + if ( m_mapTypeStats.IsValidIndex( iMapStats ) ) + { + if ( pSDO->BNilObject() ) + { + m_mapTypeStats[iMapStats].m_nNilObjects--; + } + else + { + m_mapTypeStats[iMapStats].m_nLoaded--; + } + } + + delete pSDO; + return true; + } + else + { + GJobCur().BYieldingWaitOneFrame(); + } + } + + // couldn't reload + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: A class to factor out the common code in most SDO SQL loading funcitons +//----------------------------------------------------------------------------- +template<class T> +class CSDOSQLLoadHelper +{ +public: + + //this is the results of the load helper, abstracted to provide a more efficient means to lookup the queries without + //having to do very expensive memory management + template< class SCH > + class CResults + { + public: + + //given a key, this will return the range of indices that include this key. -1 will be set to the start if no match is found. This will return + //the number of matches found + int GetKeyIndexRange( typename T::KeyType_t Key, int& nStart, int& nEnd ) const; + + //given a key, this will return the first schema that matches this key, or NULL if none is found. Only use this if you know there + //is a maximum of a single value + const SCH* GetSingleResultForKey( typename T::KeyType_t Key ) const; + + //used to start iteration over a group of results. Given a key, this will return the index that can be used to get the result or -1 if no match is found + int GetFirstResultIndex( typename T::KeyType_t Key ) const; + //given an index that was previously obtained from GetFirst/NextResultIndex, this will get the next result that has the same key, or return -1 if no further keys of that type are found + int GetNextResultIndex( int nOldResultIndex ) const; + //given an index from GetFirst/NextResultIndex that is valid (i.e. not -1), this will return the result that was obtained + const SCH* GetResultFromIndex( int nIndex ) const; + //given an index returned by the GetFirst/NextResultIndex, this will determine if it is valid + static int InvalidIndex() { return -1; } + + private: + //given a key, this maps to the specific result + template< class TKeyType > + struct SKeyToResult + { + bool operator< (const SKeyToResult< TKeyType >& rhs ) const { return m_Key < rhs.m_Key; } + typename TKeyType m_Key; //key value + uint32 m_nResultIndex; //index into our result list + }; + + friend class CSDOSQLLoadHelper; + + CUtlVector< SKeyToResult< typename T::KeyType_t > > m_KeyToResult; + CUtlVector< SCH > m_Results; + }; + + + // Initializes with the vector of objects being loaded + CSDOSQLLoadHelper( const CUtlVector<ISDO *> *vecSDOToLoad, const char *pchProfileName ); + + // Loads all rows in the SCH table whose field nFieldID match the key of an SDO being loaded + template<class SCH> + bool BYieldingExecuteSingleTable( int nFieldID, CResults< SCH >& Results ); + // Loads the specified columns for all rows in the SCH table whose field nFieldID match the key of an SDO being loaded + template<class SCH> + bool BYieldingExecuteSingleTable( int nFieldID, const CColumnSet &csetRead, CResults< SCH >& Results ); + + // Functions to load rows from more than one table at a time + + // Loads all rows in the SCH table whose field nFieldID match the key of an SDO being loaded + template<class SCH> + void AddTableToQuery( int nFieldID ); + + // Loads the specified columns for all rows in the SCH table whose field nFieldID match the key of an SDO being loaded + template<class SCH> + void AddTableToQuery( int nFieldID, const CColumnSet &csetRead ); + + // Executes the mutli-table query + bool BYieldingExecute(); + + // Gets the results for a table from a multi-table query + template<class SCH> + bool BGetResults( int nQuery, CResults< SCH >& Results ); + +private: + CUtlVector<typename T::KeyType_t> m_vecKeys; + CSQLAccess m_sqlAccess; + + struct Query_t + { + Query_t( const CColumnSet &columnSet, int nKeyCol ) : m_ColumnSet( columnSet ), m_nKeyCol( nKeyCol ) {} + CColumnSet m_ColumnSet; + int m_nKeyCol; + }; + + CUtlVector<Query_t> m_vecQueries; +}; + +//utility to help with iteration through a SQL load result list +#define FOR_EACH_SQL_LOAD_RESULT( Results, Key, Index ) for( int Index = Results.GetFirstResultIndex( Key ); Index != Results.InvalidIndex(); Index = Results.GetNextResultIndex( Index ) ) + + +//----------------------------------------------------------------------------- +// Purpose: Constructor. Initializes with the vector of objects being loaded +//----------------------------------------------------------------------------- +template<class T> +CSDOSQLLoadHelper<T>::CSDOSQLLoadHelper( const CUtlVector<ISDO *> *vecSDOToLoad, const char *pchProfileName ) + : m_vecKeys( 0, vecSDOToLoad->Count() ) +{ + FOR_EACH_VEC( *vecSDOToLoad, i ) + { + m_vecKeys.AddToTail( ( (T*)vecSDOToLoad->Element( i ) )->GetKey() ); + } + + Assert( m_vecKeys.Count() > 0 ); + DbgVerify( m_sqlAccess.BBeginTransaction( pchProfileName ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Loads all rows in the SCH table whose field nFieldID match the +// key of an SDO being loaded. +//----------------------------------------------------------------------------- +template<class T> +template<class SCH> +bool CSDOSQLLoadHelper<T>::BYieldingExecuteSingleTable( int nFieldID, CResults< SCH >& Results ) +{ + static const CColumnSet cSetRead = CColumnSet::Full<SCH>(); + return BYieldingExecuteSingleTable( nFieldID, cSetRead, Results ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Loads the specified columns for all rows in the SCH table whose +// field nFieldID match the key of an SDO being loaded +//----------------------------------------------------------------------------- +template<class T> +template<class SCH> +bool CSDOSQLLoadHelper<T>::BYieldingExecuteSingleTable( int nFieldID, const CColumnSet &csetRead, CResults< SCH >& Results ) +{ + AddTableToQuery<SCH>( nFieldID, csetRead ); + if ( !BYieldingExecute() ) + return false; + + return BGetResults<SCH>( 0, Results ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Loads all rows in the SCH table whose field nFieldID match the key +// of an SDO being loaded +//----------------------------------------------------------------------------- +template<class T> +template<class SCH> +void CSDOSQLLoadHelper<T>::AddTableToQuery( int nFieldID ) +{ + static const CColumnSet cSetRead = CColumnSet::Full<SCH>(); + AddTableToQuery<SCH>( nFieldID, cSetRead ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Loads the specified columns for all rows in the SCH table whose +// field nFieldID match the key of an SDO being loaded +//----------------------------------------------------------------------------- +template<class T> +template<class SCH> +void CSDOSQLLoadHelper<T>::AddTableToQuery( int nFieldID, const CColumnSet &csetRead ) +{ + Assert( csetRead.GetRecordInfo() == GSchemaFull().GetSchema( SCH::k_iTable ).GetRecordInfo() ); + + // Bind the params + FOR_EACH_VEC( m_vecKeys, i ) + { + m_sqlAccess.AddBindParam( m_vecKeys[i] ); + } + + // Build the query + CUtlString sCommand; + { + TSQLCmdStr sSelect; + const char *pchColumnName = GSchemaFull().GetSchema( SCH::k_iTable ).GetRecordInfo()->GetColumnInfo( nFieldID ).GetName(); + + BuildSelectStatementText( &sSelect, csetRead ); + sCommand.Format( "%s WHERE %s IN (%.*s)", sSelect.Access(), pchColumnName, GetInsertArgStringChars( m_vecKeys.Count() ), GetInsertArgString() ); + } + + // Execute. Because we're in a transaction this will delay to the commit + DbgVerify( m_sqlAccess.BYieldingExecute( NULL, sCommand ) ); + + m_vecQueries.AddToTail( Query_t( csetRead, nFieldID ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Executes the mutli-table query +//----------------------------------------------------------------------------- +template<class T> +bool CSDOSQLLoadHelper<T>::BYieldingExecute() +{ + if ( 0 == m_vecKeys.Count() ) + { + m_sqlAccess.RollbackTransaction(); + return false; + } + + if ( !m_sqlAccess.BCommitTransaction() ) + return false; + + Assert( (uint32)m_vecQueries.Count() == m_sqlAccess.GetResultSetCount() ); + return (uint32)m_vecQueries.Count() == m_sqlAccess.GetResultSetCount(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Gets the results for a table from a multi-table query +//----------------------------------------------------------------------------- +template<class T> +template<class SCH> +bool CSDOSQLLoadHelper<T>::BGetResults( int nQuery, CResults< SCH >& Results ) +{ + //clear any previous results + Results.m_KeyToResult.Purge(); + Results.m_Results.Purge(); + + IGCSQLResultSetList *pSQLResults = m_sqlAccess.GetResults(); + Assert( pSQLResults && nQuery >= 0 && (uint32)nQuery < pSQLResults->GetResultSetCount() && pSQLResults->GetResultSetCount() == (uint32)m_vecQueries.Count() ); + if ( NULL == pSQLResults || nQuery < 0 || (uint32)nQuery >= pSQLResults->GetResultSetCount() || pSQLResults->GetResultSetCount() != (uint32)m_vecQueries.Count() ) + return false; + + Assert( m_vecQueries[nQuery].m_ColumnSet.GetRecordInfo()->GetTableID() == SCH::k_iTable ); + if ( m_vecQueries[nQuery].m_ColumnSet.GetRecordInfo()->GetTableID() != SCH::k_iTable ) + return false; + + //copy the results from the SQL queries over to our result list + Results.m_Results.EnsureCapacity( pSQLResults->GetResultSet( nQuery )->GetRowCount() ); + if ( !CopyResultToSchVector( pSQLResults->GetResultSet( nQuery ), m_vecQueries[nQuery].m_ColumnSet, &Results.m_Results ) ) + return false; + + // Make a map that counts maps from our results into a sorted list for fast lookup + Results.m_KeyToResult.SetSize( Results.m_Results.Count() ); + FOR_EACH_VEC( Results.m_Results, nCurrResult ) + { + //get our key value + uint8 *pubData; + uint32 cubData; + if ( !Results.m_Results[ nCurrResult ].BGetField( m_vecQueries[nQuery].m_nKeyCol, &pubData, &cubData ) ) + return false; + + Assert( cubData == sizeof( T::KeyType_t ) ); + if ( cubData != sizeof( T::KeyType_t ) ) + { + Results.m_KeyToResult.Purge(); + Results.m_Results.Purge(); + return false; + } + + //setup our record + Results.m_KeyToResult[ nCurrResult ].m_Key = *((T::KeyType_t *)pubData); + Results.m_KeyToResult[ nCurrResult ].m_nResultIndex = nCurrResult; + } + + //sort for binary search capabilities + std::sort( Results.m_KeyToResult.begin(), Results.m_KeyToResult.end() ); + + return true; +} + +template< typename T > +template< typename SCH > +int CSDOSQLLoadHelper< T >::CResults< SCH >::GetKeyIndexRange( typename T::KeyType_t Key, int& nStart, int& nEnd ) const +{ + //find the start of the range + nStart = GetFirstResultIndex( Key ); + if( nStart == InvalidIndex() ) + { + nEnd = InvalidIndex(); + return 0; + } + //expand the end as long as it lies on a key in range and with the same value + for( nEnd = nStart + 1; ( nEnd < m_KeyToResult.Count() ) && ( m_KeyToResult[ nEnd ].m_Key == Key ); nEnd++ ) + { + } + //and return the resulting number of elements we found + return nEnd - nStart; +} + + +template< typename T > +template< typename SCH > +const SCH* CSDOSQLLoadHelper< T >::CResults< SCH >::GetSingleResultForKey( typename T::KeyType_t Key ) const +{ + int nIndex = GetFirstResultIndex( Key ); + AssertMsg( ( nIndex == InvalidIndex() ) || ( GetNextResultIndex( nIndex ) == InvalidIndex() ), "Requested a result from a SQL load helper assuming it was a singular entry, but found multiple instances of it" ); + return GetResultFromIndex( nIndex ); +} + +template< typename T > +template< typename SCH > +int CSDOSQLLoadHelper< T >::CResults< SCH >::GetFirstResultIndex( typename T::KeyType_t Key ) const +{ + //dummy entry to compare against + SKeyToResult< typename T::KeyType_t > SearchKey; + SearchKey.m_Key = Key; + + //binary search to find our index + const SKeyToResult< typename T::KeyType_t >* pMatch = std::lower_bound( m_KeyToResult.begin(), m_KeyToResult.end(), SearchKey ); + //see if we found a match + if( ( pMatch == m_KeyToResult.end() ) || ( pMatch->m_Key != Key ) ) + return InvalidIndex(); + + return ( pMatch - m_KeyToResult.begin() ); +} + +template< typename T > +template< typename SCH > +int CSDOSQLLoadHelper< T >::CResults< SCH >::GetNextResultIndex( int nOldResultIndex ) const +{ + //handle out of range elements. Either invalid, or the last element or beyond in our array + if( ( nOldResultIndex < 0 ) || ( nOldResultIndex + 1 >= m_KeyToResult.Count() ) ) + return InvalidIndex(); + + //see if we are less than the next value in the list. If so, we aren't equal and need to stop iteration + if( m_KeyToResult[ nOldResultIndex ] < m_KeyToResult[ nOldResultIndex + 1 ] ) + return InvalidIndex(); + + //same key for the next element, so return a match + return nOldResultIndex + 1; +} + +template< typename T > +template< typename SCH > +const SCH* CSDOSQLLoadHelper< T >::CResults< SCH >::GetResultFromIndex( int nIndex ) const +{ + //ensure that the index is in range + if( ( nIndex < 0 ) || ( nIndex >= m_KeyToResult.Count() ) ) + return NULL; + return &( m_Results[ m_KeyToResult[ nIndex ].m_nResultIndex ] ); +} + +} // namespace GCSDK + +#endif // SDOCACHE_H |