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.

1190 lines
39 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Serialized Digital Object caching and manipulation
  4. //
  5. //=============================================================================
  6. #ifndef SBOCACHE_H
  7. #define SBOCACHE_H
  8. #ifdef _WIN32
  9. #pragma once
  10. #endif
  11. #include "tier1/utlhashmaplarge.h"
  12. #include "tier1/utlqueue.h"
  13. #include "tier1/utlvector.h"
  14. #include <algorithm>
  15. namespace GCSDK
  16. {
  17. // Call to register SDOs. All SDO types must be registered before loaded
  18. #define REG_SDO( classname ) GSDOCache().RegisterSDO( classname::k_eType, #classname )
  19. // A string used to tell the difference between nil objects and actual objects in memcached
  20. extern const char k_rgchNilObjSerializedValue[];
  21. //-----------------------------------------------------------------------------
  22. // Purpose: Keeps a moving average of a data set
  23. //-----------------------------------------------------------------------------
  24. template< int SAMPLES >
  25. class CMovingAverage
  26. {
  27. public:
  28. CMovingAverage()
  29. {
  30. Reset();
  31. }
  32. void Reset()
  33. {
  34. memset( m_rglSamples, 0, sizeof( m_rglSamples ) );
  35. m_cSamples = 0;
  36. m_lTotal = 0;
  37. }
  38. void AddSample( int64 lSample )
  39. {
  40. int iIndex = m_cSamples % SAMPLES;
  41. m_lTotal += ( lSample - m_rglSamples[iIndex] );
  42. m_rglSamples[iIndex] = lSample;
  43. m_cSamples++;
  44. }
  45. uint64 GetAveragedSample() const
  46. {
  47. if ( !m_cSamples )
  48. return 0;
  49. int64 iMax = (int64)MIN( m_cSamples, SAMPLES );
  50. return m_lTotal / iMax;
  51. }
  52. private:
  53. int64 m_rglSamples[SAMPLES];
  54. int64 m_lTotal;
  55. uint64 m_cSamples;
  56. };
  57. //-----------------------------------------------------------------------------
  58. // Purpose: Global accessor to the manager
  59. //-----------------------------------------------------------------------------
  60. class CSDOCache;
  61. CSDOCache &GSDOCache();
  62. //-----------------------------------------------------------------------------
  63. // Purpose: interface to a Database Backed Object
  64. //-----------------------------------------------------------------------------
  65. class ISDO
  66. {
  67. public:
  68. virtual ~ISDO() {}
  69. // Identification
  70. virtual int GetType() const = 0;
  71. virtual uint32 GetHashCode() const = 0;
  72. virtual bool IsEqual( const ISDO *pSDO ) const = 0;
  73. // Ref counting
  74. virtual int AddRef() = 0;
  75. virtual int Release() = 0;
  76. virtual int GetRefCount() = 0;
  77. // memory usage
  78. virtual size_t CubBytesUsed() = 0;
  79. // Serialization tools
  80. virtual bool BReadFromBuffer( const byte *pubData, int cubData ) = 0;
  81. virtual void WriteToBuffer( CUtlBuffer &memBuffer ) = 0;
  82. // memcached batching tools
  83. virtual void GetMemcachedKeyName( CFmtStr &strDest ) = 0;
  84. // SQL loading
  85. virtual bool BYldLoadFromSQL( CUtlVector<ISDO *> &vecSDOToLoad, CUtlVector<bool> &vecResults ) const = 0;
  86. // post-load initialization (whether loaded from SQL or memcached)
  87. virtual void PostLoadInit() = 0;
  88. // comparison function for validating memcached copies vs SQL copies
  89. virtual bool IsIdentical( ISDO *pSDO ) = 0;
  90. // Returns true if this is not the actual object, but a placeholder to remember that this object
  91. // doesn't actually exist
  92. virtual bool BNilObject() const = 0;
  93. // Allocs an SDO that must return true for IsEqual( this ) and BNilObject(). This is so we can
  94. // cache load attempts for objects that don't exist
  95. virtual ISDO *AllocNilObject() = 0;
  96. // Creates a key name that looks like "Prefix_%u" but faster
  97. };
  98. //-----------------------------------------------------------------------------
  99. // Purpose: base class for a Serialized Digital Object
  100. //-----------------------------------------------------------------------------
  101. template<typename KeyType, int eSDOType, class ProtoMsg>
  102. class CBaseSDO : public ISDO
  103. {
  104. public:
  105. typedef KeyType KeyType_t;
  106. enum { k_eType = eSDOType };
  107. CBaseSDO( const KeyType &key ) : m_Key( key ), m_nRefCount( 0 ) {}
  108. const KeyType &GetKey() const { return m_Key; }
  109. // ISDO implementation
  110. virtual int AddRef();
  111. virtual int Release();
  112. virtual int GetRefCount();
  113. virtual int GetType() const { return eSDOType; }
  114. virtual bool BNilObject() const { return false; }
  115. virtual uint32 GetHashCode() const;
  116. virtual bool BReadFromBuffer( const byte *pubData, int cubData );
  117. virtual void WriteToBuffer( CUtlBuffer &memBuffer );
  118. // Implement this in a subclass if there's some value like 0 or -1 that's invalid so
  119. // the system can know to not even attempt to load it
  120. static bool BKeyValid( const KeyType_t &key ) { return true; }
  121. // We use protobufs for all serialization
  122. virtual void SerializeToProtobuf( ProtoMsg &msg ) = 0;
  123. virtual bool DeserializeFromProtobuf( const ProtoMsg &msg ) = 0;
  124. // default comparison function - override to do your own compare
  125. virtual bool IsEqual( const ISDO *pSDO ) const;
  126. // default load from SQL is no-op as not all types have permanent storage - override to create a
  127. // batch load
  128. virtual bool BYldLoadFromSQL( CUtlVector<ISDO *> &vecSDOToLoad, CUtlVector<bool> &vecResults ) const;
  129. // override to do initialization after load
  130. virtual void PostLoadInit() {}
  131. // compares the serialized versions by default. Override to have more specific behavior
  132. virtual bool IsIdentical( ISDO *pSDO );
  133. // Creates a copy of the object with the same key
  134. virtual ISDO *AllocNilObject();
  135. // tools
  136. bool WriteToMemcached();
  137. bool DeleteFromMemcached();
  138. // Creates a key name that looks like "Prefix_%u" but faster. Also makes sure the correct buffer size is passed
  139. template <size_t prefixBufSize>
  140. void CreateSimpleMemcachedName( CFmtStr &strDest, char (&rgchPrefix)[prefixBufSize], uint32 unSuffix )
  141. {
  142. CSDOCache::CreateSimpleMemcachedName( strDest, rgchPrefix, prefixBufSize - 1, unSuffix );
  143. }
  144. private:
  145. int m_nRefCount;
  146. KeyType m_Key;
  147. };
  148. //-----------------------------------------------------------------------------
  149. // Purpose: Represents an object we tried to load but didn't exist. We use
  150. // this to cache the failure so we don't keep trying to look it up
  151. //-----------------------------------------------------------------------------
  152. template<typename KeyType, int eSDOType, class ProtoMsg>
  153. class CNilSDO : public CBaseSDO<KeyType, eSDOType, ProtoMsg>
  154. {
  155. public:
  156. CNilSDO( const KeyType &key, const char *pchMemcachedKeyName ) : CBaseSDO<KeyType, eSDOType, ProtoMsg>( key ), m_sMemcachedKeyName( pchMemcachedKeyName ) {}
  157. virtual bool BNilObject() const { return true; }
  158. virtual bool BReadFromBuffer( const byte *pubData, int cubData ) { return false; }
  159. virtual void WriteToBuffer( CUtlBuffer &memBuffer ) { memBuffer.PutString( k_rgchNilObjSerializedValue ); }
  160. virtual void SerializeToProtobuf( ProtoMsg &msg ) {}
  161. virtual bool DeserializeFromProtobuf( const ProtoMsg &msg ) { return false; }
  162. virtual size_t CubBytesUsed() { return sizeof( *this ) + m_sMemcachedKeyName.Length(); }
  163. virtual void GetMemcachedKeyName( CFmtStr &strKey ) { V_strncpy( strKey.Access(), m_sMemcachedKeyName, FMTSTR_STD_LEN ); }
  164. private:
  165. CUtlString m_sMemcachedKeyName;
  166. };
  167. //-----------------------------------------------------------------------------
  168. // Purpose: references to a database-backed object
  169. // maintains refcount of the object
  170. //-----------------------------------------------------------------------------
  171. template<class T>
  172. class CSDORef
  173. {
  174. T *m_pSDO;
  175. public:
  176. CSDORef() { m_pSDO = NULL; }
  177. explicit CSDORef( CSDORef<T> &SDORef ) { m_pSDO = SDORef.Get(); if ( m_pSDO ) m_pSDO->AddRef(); }
  178. explicit CSDORef( T *pSDO ) { m_pSDO = pSDO; if ( m_pSDO ) m_pSDO->AddRef(); }
  179. ~CSDORef() { if ( m_pSDO ) m_pSDO->Release(); }
  180. T *Get() { return m_pSDO; }
  181. const T *Get() const { return m_pSDO; }
  182. T *operator->() { return Get(); }
  183. const T *operator->() const { return Get(); }
  184. operator const T *() const { return m_pSDO; }
  185. operator const T *() { return m_pSDO; }
  186. operator T *() { return m_pSDO; }
  187. CSDORef<T> &operator=( T *pSDO ) { if ( m_pSDO ) m_pSDO->Release(); m_pSDO = pSDO; if ( m_pSDO ) m_pSDO->AddRef(); return *this; }
  188. bool operator !() const { return Get() == NULL; }
  189. bool IsValid( void ) const { return Get() != NULL; }
  190. };
  191. //-----------------------------------------------------------------------------
  192. // Purpose: manages a cache of SDO objects
  193. //-----------------------------------------------------------------------------
  194. class CSDOCache
  195. {
  196. public:
  197. CSDOCache();
  198. ~CSDOCache();
  199. // Call to register SDOs. All SDO types must be registered before loaded
  200. void RegisterSDO( int nType, const char *pchName );
  201. // A struct to hold stats for the system. This is generated code in Steam. It would be great to make
  202. // it generated code here if we could bring Steam's operational stats system in the GC
  203. struct StatsSDOCache_t
  204. {
  205. uint64 m_cItemsLRUd;
  206. uint64 m_cBytesLRUd;
  207. uint64 m_cItemsUnreferenced;
  208. uint64 m_cBytesUnreferenced;
  209. uint64 m_cItemsInCache;
  210. uint64 m_cBytesInCacheEst;
  211. uint64 m_cItemsQueuedToLoad;
  212. uint64 m_cItemsLoadedFromMemcached;
  213. uint64 m_cItemsLoadedFromSQL;
  214. uint64 m_cItemsFailedLoadFromSQL;
  215. uint64 m_cQueuedMemcachedRequests;
  216. uint64 m_cQueuedSQLRequests;
  217. uint64 m_nSQLBatchSizeAvgx100;
  218. uint64 m_nMemcachedBatchSizeAvgx100;
  219. uint64 m_cSQLRequestsRejectedTooBusy;
  220. uint64 m_cMemcachedRequestsRejectedTooBusy;
  221. uint64 m_cNilItemsLoadedFromMemcached;
  222. uint64 m_cNilItemsLoadedFromSQL;
  223. };
  224. // loads a SDO, and assigns a reference to it
  225. // returns false if the item couldn't be loaded, or timed out loading
  226. template<class T>
  227. bool BYldLoadSDO( CSDORef<T> *pPSDORef, const typename T::KeyType_t &key, bool *pbTimeoutLoading = NULL );
  228. // gets access to a SDO, but only if it's currently loaded
  229. template<class T>
  230. bool BGetLoadedSDO( CSDORef<T> *pPSDORef, const typename T::KeyType_t &key, bool *pbFoundNil = NULL );
  231. // starts loading a SDO you're going to reference soon with the above BYldLoadSDO()
  232. // use this to batch up requests, hinting a set then getting reference to a set is significantly faster
  233. template<class T>
  234. void HintLoadSDO( const typename T::KeyType_t &key );
  235. // as above, but starts load a set
  236. template<class T>
  237. void HintLoadSDO( const CUtlVector<typename T::KeyType_t> &vecKeys );
  238. // Clears a nil object if one exists for this key.
  239. template<class T>
  240. void RemoveNil( const typename T::KeyType_t &key );
  241. // force a deletes a SDO from the cache - waits until the object is not referenced
  242. template<class T>
  243. bool BYldDeleteSDO( const typename T::KeyType_t &key, uint64 unMicrosecondsToWaitForUnreferenced );
  244. // SDO refcount management
  245. void OnSDOReferenced( ISDO *pSDO );
  246. void OnSDOReleased( ISDO *pSDO );
  247. // writes a SDO to memcached immediately
  248. bool WriteSDOToMemcached( ISDO *pSDO );
  249. // delete the SDO record from memcached
  250. bool DeleteSDOFromMemcached( ISDO *pSDO );
  251. // job results
  252. void OnSDOLoadSuccess( int eSDO, int iRequestID, bool bNilObj, ISDO **ppSDO );
  253. void OnMemcachedSDOLoadFailure( int eSDO, int iRequestID );
  254. void OnSQLSDOLoadFailure( int eSDO, int iRequestID, bool bSQLLayerSucceeded );
  255. void OnMemcachedLoadJobComplete( JobID_t jobID );
  256. void OnSQLLoadJobComplete( int eSDO, JobID_t jobID );
  257. // test access - deletes all unreferenced objects
  258. void Flush();
  259. // stats access
  260. StatsSDOCache_t &GetStats() { return m_StatsSDOCache; }
  261. int CubReferencedEst(); // number of bytes referenced in the cache
  262. // prints info about the class
  263. void Dump();
  264. static void CreateSimpleMemcachedName( CFmtStr &strDest, const char *pchPrefix, uint32 unPrefixLen, uint32 unSuffix );
  265. // memcached verification - returns the number of mismatches
  266. //**tempcomment** void YldVerifyMemcachedData( CreateSDOFunc_t pCreateSDOFunc, CUtlVector<uint32> &vecIDs, int *pcMatches, int *pcMismatches );
  267. #ifdef DBGFLAG_VALIDATE
  268. void Validate( CValidator &validator, const char *pchName );
  269. #endif
  270. // Functions that need to be in the frame loop
  271. virtual bool BFrameFuncRunJobsUntilCompleted( CLimitTimer &limitTimer );
  272. virtual bool BFrameFuncRunMemcachedQueriesUntilCompleted( CLimitTimer &limitTimer );
  273. virtual bool BFrameFuncRunSQLQueriesUntilCompleted( CLimitTimer &limitTimer );
  274. private:
  275. // Custom comparator for our hash map
  276. class CDefPISDOEquals
  277. {
  278. public:
  279. CDefPISDOEquals() {}
  280. CDefPISDOEquals( int i ) {}
  281. inline bool operator()( const ISDO *lhs, const ISDO *rhs ) const { return ( lhs->IsEqual( rhs ) ); }
  282. inline bool operator!() const { return false; }
  283. };
  284. class CPISDOHashFunctor
  285. {
  286. public:
  287. uint32 operator()(const ISDO *pSDO ) const { return pSDO->GetHashCode(); }
  288. };
  289. template<class T>
  290. int FindLoadedSDO( const typename T::KeyType_t &key );
  291. template<class T>
  292. int QueueLoad( const typename T::KeyType_t &key );
  293. int QueueMemcachedLoad( ISDO *pSDO );
  294. // items already loaded - Maps the SDO to the LRU position
  295. CUtlHashMapLarge<ISDO *, int, CDefPISDOEquals, CPISDOHashFunctor> m_mapISDOLoaded;
  296. // items we have queued to load, in the state of either being loaded from memcached or SQL
  297. // maps SDO to a list of jobs waiting on the load
  298. CUtlHashMapLarge<ISDO *, CCopyableUtlVector<JobID_t>, CDefPISDOEquals, CPISDOHashFunctor> m_mapQueuedRequests;
  299. // requests to load from memcached
  300. CUtlLinkedList<int, int> m_queueMemcachedRequests;
  301. // Jobs currently processing memcached load requests
  302. CUtlVector<JobID_t> m_vecMemcachedJobs;
  303. // Loading from SQL is divided by SDO type
  304. struct SQLRequestManager_t
  305. {
  306. // requests to load from SQL. Maps to an ID in the map of queued requests
  307. CUtlLinkedList<int, int> m_queueRequestIDsToLoadFromSQL;
  308. // SQL jobs we have active doing reads for cache items
  309. CUtlVector<JobID_t> m_vecSQLJobs;
  310. };
  311. // a queue of requests to load from SQL for each type
  312. CUtlHashMapLarge<int, SQLRequestManager_t *> m_mapQueueSQLRequests;
  313. // jobs to wake up, since we've satisfied their SDO load request
  314. struct JobToWake_t
  315. {
  316. JobID_t m_jobID;
  317. bool m_bLoadLayerSuccess;
  318. };
  319. CUtlLinkedList<JobToWake_t, int> m_queueJobsToContinue;
  320. struct LRUItem_t
  321. {
  322. ISDO * m_pSDO;
  323. size_t m_cub;
  324. };
  325. CUtlLinkedList<LRUItem_t, int> m_listLRU;
  326. uint32 m_cubLRUItems;
  327. void RemoveSDOFromLRU( int iMapSDOLoaded );
  328. struct TypeStats_t
  329. {
  330. TypeStats_t()
  331. : m_nLoaded( 0 )
  332. , m_nRefed( 0 )
  333. , m_cubUnrefed( 0 )
  334. , m_nNilObjects( 0 )
  335. {}
  336. CUtlString m_strName;
  337. int m_nLoaded;
  338. int m_nRefed;
  339. int m_cubUnrefed;
  340. int m_nNilObjects;
  341. };
  342. StatsSDOCache_t m_StatsSDOCache;
  343. CMovingAverage<100> m_StatMemcachedBatchSize, m_StatSQLBatchSize;
  344. CUtlMap<int, TypeStats_t> m_mapTypeStats;
  345. };
  346. //-----------------------------------------------------------------------------
  347. // Definition of CBaseSDO template functions now that CSDOCache is defined and
  348. // GSDOCache() can safely be used.
  349. //-----------------------------------------------------------------------------
  350. //-----------------------------------------------------------------------------
  351. // Purpose: adds a reference
  352. //-----------------------------------------------------------------------------
  353. template<typename KeyType, int ESDOType, class ProtoMsg>
  354. int CBaseSDO<KeyType,ESDOType,ProtoMsg>::AddRef()
  355. {
  356. if ( ++m_nRefCount == 1 )
  357. GSDOCache().OnSDOReferenced( this );
  358. return m_nRefCount;
  359. }
  360. //-----------------------------------------------------------------------------
  361. // Purpose: releases a reference
  362. //-----------------------------------------------------------------------------
  363. template<typename KeyType, int ESDOType, class ProtoMsg>
  364. int CBaseSDO<KeyType,ESDOType,ProtoMsg>::Release()
  365. {
  366. DbgVerify( m_nRefCount > 0 );
  367. int nRefCount = --m_nRefCount;
  368. if ( nRefCount == 0 )
  369. GSDOCache().OnSDOReleased( this );
  370. return nRefCount;
  371. }
  372. //-----------------------------------------------------------------------------
  373. // Purpose: ref count
  374. //-----------------------------------------------------------------------------
  375. template<typename KeyType, int ESDOType, class ProtoMsg>
  376. int CBaseSDO<KeyType,ESDOType,ProtoMsg>::GetRefCount()
  377. {
  378. return m_nRefCount;
  379. }
  380. //-----------------------------------------------------------------------------
  381. // Purpose: Hashes the object for insertion into a hashtable
  382. //-----------------------------------------------------------------------------
  383. template<typename KeyType, int ESDOType, class ProtoMsg>
  384. uint32 CBaseSDO<KeyType,ESDOType,ProtoMsg>::GetHashCode() const
  385. {
  386. #pragma pack( push, 1 )
  387. struct hashcode_t
  388. {
  389. int m_Type;
  390. KeyType_t m_Key;
  391. } hashStruct = { ESDOType, m_Key };
  392. #pragma pack( pop )
  393. return PearsonsHashFunctor<hashcode_t>()( hashStruct );
  394. }
  395. //-----------------------------------------------------------------------------
  396. // Purpose: Deserializes the object
  397. //-----------------------------------------------------------------------------
  398. template<typename KeyType, int ESDOType, class ProtoMsg>
  399. bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::BReadFromBuffer( const byte *pubData, int cubData )
  400. {
  401. ProtoMsg msg;
  402. if ( !msg.ParseFromArray( pubData, cubData ) )
  403. return false;
  404. if ( !DeserializeFromProtobuf( msg ) )
  405. return false;
  406. return true;
  407. }
  408. //-----------------------------------------------------------------------------
  409. // Purpose: Serializes the object
  410. //-----------------------------------------------------------------------------
  411. template<typename KeyType, int ESDOType, class ProtoMsg>
  412. void CBaseSDO<KeyType,ESDOType,ProtoMsg>::WriteToBuffer( CUtlBuffer &memBuffer )
  413. {
  414. ProtoMsg *pMsg = CProtoBufMsg<ProtoMsg>::AllocProto();
  415. SerializeToProtobuf( *pMsg );
  416. uint32 unSize = pMsg->ByteSize();
  417. memBuffer.EnsureCapacity( memBuffer.Size() + unSize );
  418. pMsg->SerializeWithCachedSizesToArray( (uint8*)memBuffer.Base() + memBuffer.TellPut() );
  419. memBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, memBuffer.TellPut() + unSize );
  420. CProtoBufMsg<ProtoMsg>::FreeProto( pMsg );
  421. }
  422. //-----------------------------------------------------------------------------
  423. // Purpose: does an immediate write of the object to memcached
  424. //-----------------------------------------------------------------------------
  425. template<typename KeyType, int ESDOType, class ProtoMsg>
  426. bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::WriteToMemcached()
  427. {
  428. return GSDOCache().WriteSDOToMemcached( this );
  429. }
  430. //-----------------------------------------------------------------------------
  431. // Purpose: does an immediate write of the object to memcached
  432. //-----------------------------------------------------------------------------
  433. template<typename KeyType, int ESDOType, class ProtoMsg>
  434. bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::DeleteFromMemcached()
  435. {
  436. return GSDOCache().DeleteSDOFromMemcached( this );
  437. }
  438. //-----------------------------------------------------------------------------
  439. // Purpose: default equality function - compares type and key
  440. //-----------------------------------------------------------------------------
  441. template<typename KeyType, int ESDOType, class ProtoMsg>
  442. bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::IsEqual( const ISDO *pSDO ) const
  443. {
  444. if ( GetType() != pSDO->GetType() )
  445. return false;
  446. return ( GetKey() == static_cast<const CBaseSDO<KeyType,ESDOType,ProtoMsg> *>( pSDO )->GetKey() );
  447. }
  448. //-----------------------------------------------------------------------------
  449. // Purpose: Batch load a group of SDO's of the same type from SQL.
  450. // Default is no-op as not all types have permanent storage.
  451. //-----------------------------------------------------------------------------
  452. template<typename KeyType, int ESDOType, class ProtoMsg>
  453. bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::BYldLoadFromSQL( CUtlVector<ISDO *> &vecSDOToLoad, CUtlVector<bool> &vecResults ) const
  454. {
  455. FOR_EACH_VEC( vecResults, i )
  456. {
  457. vecResults[i] = true;
  458. }
  459. return true;
  460. }
  461. //-----------------------------------------------------------------------------
  462. // Purpose: default validation function - compares serialized versions
  463. //-----------------------------------------------------------------------------
  464. bool CompareSDOObjects( ISDO *pSDO1, ISDO *pSDO2 );
  465. template<typename KeyType, int ESDOType, class ProtoMsg>
  466. bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::IsIdentical( ISDO *pSDO )
  467. {
  468. return CompareSDOObjects( this, pSDO );
  469. }
  470. //-----------------------------------------------------------------------------
  471. // Purpose: default validation function - compares serialized versions
  472. //-----------------------------------------------------------------------------
  473. template<typename KeyType, int ESDOType, class ProtoMsg>
  474. ISDO *CBaseSDO<KeyType,ESDOType,ProtoMsg>::AllocNilObject()
  475. {
  476. CFmtStr strKey;
  477. GetMemcachedKeyName( strKey );
  478. return new CNilSDO<KeyType,ESDOType,ProtoMsg>( GetKey(), strKey );
  479. }
  480. //-----------------------------------------------------------------------------
  481. // Purpose: Finds a loaded SDO in memory. Returns the index of the object
  482. // into the loaded SDOs map
  483. //-----------------------------------------------------------------------------
  484. template<class T>
  485. int CSDOCache::FindLoadedSDO( const typename T::KeyType_t &key )
  486. {
  487. // see if we have it in cache first
  488. T probe( key );
  489. return m_mapISDOLoaded.Find( &probe );
  490. }
  491. //-----------------------------------------------------------------------------
  492. // Purpose: Queues loading an SDO. Returns the index of the entry in the
  493. // load queue
  494. //-----------------------------------------------------------------------------
  495. template<class T>
  496. int CSDOCache::QueueLoad( const typename T::KeyType_t &key )
  497. {
  498. T probe( key );
  499. int iMap = m_mapQueuedRequests.Find( &probe );
  500. if ( m_mapQueuedRequests.IsValidIndex( iMap ) )
  501. return iMap;
  502. return QueueMemcachedLoad( new T( key ) );
  503. }
  504. //-----------------------------------------------------------------------------
  505. // Purpose: Preloads the object into the local cache
  506. //-----------------------------------------------------------------------------
  507. template<class T>
  508. void CSDOCache::HintLoadSDO( const typename T::KeyType_t &key )
  509. {
  510. // see if this is something we should even try to load
  511. if ( !T::BKeyValid( key ) )
  512. return;
  513. // see if we have it in cache first
  514. if ( !m_mapISDOLoaded.IsValidIndex( FindLoadedSDO<T>( key ) ) )
  515. {
  516. QueueLoad<T>( key );
  517. }
  518. }
  519. //-----------------------------------------------------------------------------
  520. // Purpose: Preloads a set set of objects into the local cache
  521. //-----------------------------------------------------------------------------
  522. template<class T>
  523. void CSDOCache::HintLoadSDO( const CUtlVector<typename T::KeyType_t> &vecKeys )
  524. {
  525. FOR_EACH_VEC( vecKeys, i )
  526. {
  527. HintLoadSDO<T>( vecKeys[i] );
  528. }
  529. }
  530. //-----------------------------------------------------------------------------
  531. // Purpose: Returns an already-loaded SDO
  532. //-----------------------------------------------------------------------------
  533. template<class T>
  534. bool CSDOCache::BGetLoadedSDO( CSDORef<T> *pPSDORef, const typename T::KeyType_t &key, bool *pbFoundNil )
  535. {
  536. if ( NULL != pbFoundNil )
  537. {
  538. *pbFoundNil = false;
  539. }
  540. int iMap = FindLoadedSDO<T>( key );
  541. if ( !m_mapISDOLoaded.IsValidIndex( iMap ) )
  542. return false;
  543. ISDO *pObj = m_mapISDOLoaded.Key( iMap );
  544. if ( pObj->BNilObject() )
  545. {
  546. int iLRU = m_mapISDOLoaded[ iMap ];
  547. Assert( m_listLRU.IsValidIndex( iLRU ) );
  548. if ( m_listLRU.IsValidIndex( iLRU ) )
  549. {
  550. // Even though we don't return nil objects, this is a hit on it
  551. // so it needs to go to the back of the LRU
  552. m_listLRU.LinkToTail( m_mapISDOLoaded[ iMap ] );
  553. }
  554. if ( NULL != pbFoundNil )
  555. {
  556. *pbFoundNil = true;
  557. }
  558. return false;
  559. }
  560. else
  561. {
  562. *pPSDORef = assert_cast<T*>( pObj );
  563. return true;
  564. }
  565. }
  566. //-----------------------------------------------------------------------------
  567. // Purpose: Loads the object into memory
  568. //-----------------------------------------------------------------------------
  569. template<class T>
  570. bool CSDOCache::BYldLoadSDO( CSDORef<T> *pPSDORef, const typename T::KeyType_t &key, bool *pbTimeoutLoading /* = NULL */ )
  571. {
  572. VPROF_BUDGET( "CSDOCache::BYldLoadSDO", VPROF_BUDGETGROUP_STEAM );
  573. if ( pbTimeoutLoading )
  574. *pbTimeoutLoading = false;
  575. // Clear the current object the ref is holding
  576. *pPSDORef = NULL;
  577. // see if this is something we should even try to load
  578. if ( !T::BKeyValid( key ) )
  579. return false;
  580. // see if we have it in cache first
  581. bool bFoundNil = false;
  582. if ( BGetLoadedSDO( pPSDORef, key, &bFoundNil ) )
  583. return true;
  584. // If we've already tried to look this up in the past don't bother looking it up again
  585. if ( bFoundNil )
  586. return false;
  587. // otherwise batch it for load
  588. int iMap = QueueLoad<T>( key );
  589. // make sure we could queue it
  590. if ( !m_mapQueuedRequests.IsValidIndex( iMap ) )
  591. return false;
  592. // add the current job to this list waiting for the object to load
  593. m_mapQueuedRequests[iMap].AddToTail( GJobCur().GetJobID() );
  594. // wait for it to load (loader will signal our job when done)
  595. if ( !GJobCur().BYieldingWaitForWorkItem() )
  596. {
  597. if ( pbTimeoutLoading )
  598. *pbTimeoutLoading = true;
  599. return false;
  600. }
  601. // should be loaded - look up in the load map and try again
  602. bool bRet = BGetLoadedSDO( pPSDORef, key );
  603. //Assert( bRet );
  604. return bRet;
  605. }
  606. //-----------------------------------------------------------------------------
  607. // Clears a nil object if one exists for this key.
  608. //-----------------------------------------------------------------------------
  609. template<class T>
  610. void CSDOCache::RemoveNil( const typename T::KeyType_t &key )
  611. {
  612. // See if this key is allowed to exist
  613. if ( !T::BKeyValid( key ) )
  614. {
  615. AssertMsg( false, "RemoveNil called with an invalid key" );
  616. return;
  617. }
  618. // Remove the nil if there is one
  619. int iMap = FindLoadedSDO< T >( key );
  620. if ( m_mapISDOLoaded.IsValidIndex( iMap ) )
  621. {
  622. ISDO *pSDO = m_mapISDOLoaded.Key( iMap );
  623. if ( !pSDO->BNilObject() )
  624. return;
  625. int iMapStats = m_mapTypeStats.Find( pSDO->GetType() );
  626. if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
  627. {
  628. m_mapTypeStats[iMapStats].m_nNilObjects--;
  629. }
  630. RemoveSDOFromLRU( iMap );
  631. m_mapISDOLoaded.RemoveAt( iMap );
  632. delete pSDO;
  633. }
  634. // Remove the object from memcached. Do this here because while we have for sure removed any nil that was in memory
  635. // this may have been a nil that was not in memory but instead cached in memcached.
  636. T temp( key );
  637. DeleteSDOFromMemcached( &temp );
  638. }
  639. //-----------------------------------------------------------------------------
  640. // Purpose: reloads an existing element from the SQL DB
  641. //-----------------------------------------------------------------------------
  642. template<class T>
  643. bool CSDOCache::BYldDeleteSDO( const typename T::KeyType_t &key, uint64 unMicrosecondsToWaitForUnreferenced )
  644. {
  645. // see if we have it in cache first
  646. int iMap = FindLoadedSDO<T>( key );
  647. if ( !m_mapISDOLoaded.IsValidIndex( iMap ) )
  648. {
  649. T temp( key );
  650. temp.DeleteFromMemcached();
  651. return true; /* we're good, it's not loaded */
  652. }
  653. assert_cast<T *>(m_mapISDOLoaded.Key( iMap ))->DeleteFromMemcached();
  654. // check the ref count
  655. int64 cAttempts = MAX( 1LL, (int64)(unMicrosecondsToWaitForUnreferenced / k_cMicroSecPerShellFrame) );
  656. while ( cAttempts-- > 0 )
  657. {
  658. if ( 0 == m_mapISDOLoaded.Key( iMap )->GetRefCount() )
  659. {
  660. // delete the object
  661. Assert( m_listLRU.IsValidIndex( m_mapISDOLoaded[iMap] ) );
  662. RemoveSDOFromLRU( iMap );
  663. ISDO *pSDO = m_mapISDOLoaded.Key( iMap );
  664. m_mapISDOLoaded.RemoveAt( iMap );
  665. int iMapStats = m_mapTypeStats.Find( m_mapISDOLoaded.Key( iMap )->GetType() );
  666. if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
  667. {
  668. if ( pSDO->BNilObject() )
  669. {
  670. m_mapTypeStats[iMapStats].m_nNilObjects--;
  671. }
  672. else
  673. {
  674. m_mapTypeStats[iMapStats].m_nLoaded--;
  675. }
  676. }
  677. delete pSDO;
  678. return true;
  679. }
  680. else
  681. {
  682. GJobCur().BYieldingWaitOneFrame();
  683. }
  684. }
  685. // couldn't reload
  686. return false;
  687. }
  688. //-----------------------------------------------------------------------------
  689. // Purpose: A class to factor out the common code in most SDO SQL loading funcitons
  690. //-----------------------------------------------------------------------------
  691. template<class T>
  692. class CSDOSQLLoadHelper
  693. {
  694. public:
  695. //this is the results of the load helper, abstracted to provide a more efficient means to lookup the queries without
  696. //having to do very expensive memory management
  697. template< class SCH >
  698. class CResults
  699. {
  700. public:
  701. //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
  702. //the number of matches found
  703. int GetKeyIndexRange( typename T::KeyType_t Key, int& nStart, int& nEnd ) const;
  704. //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
  705. //is a maximum of a single value
  706. const SCH* GetSingleResultForKey( typename T::KeyType_t Key ) const;
  707. //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
  708. int GetFirstResultIndex( typename T::KeyType_t Key ) const;
  709. //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
  710. int GetNextResultIndex( int nOldResultIndex ) const;
  711. //given an index from GetFirst/NextResultIndex that is valid (i.e. not -1), this will return the result that was obtained
  712. const SCH* GetResultFromIndex( int nIndex ) const;
  713. //given an index returned by the GetFirst/NextResultIndex, this will determine if it is valid
  714. static int InvalidIndex() { return -1; }
  715. private:
  716. //given a key, this maps to the specific result
  717. template< class TKeyType >
  718. struct SKeyToResult
  719. {
  720. bool operator< (const SKeyToResult< TKeyType >& rhs ) const { return m_Key < rhs.m_Key; }
  721. typename TKeyType m_Key; //key value
  722. uint32 m_nResultIndex; //index into our result list
  723. };
  724. friend class CSDOSQLLoadHelper;
  725. CUtlVector< SKeyToResult< typename T::KeyType_t > > m_KeyToResult;
  726. CUtlVector< SCH > m_Results;
  727. };
  728. // Initializes with the vector of objects being loaded
  729. CSDOSQLLoadHelper( const CUtlVector<ISDO *> *vecSDOToLoad, const char *pchProfileName );
  730. // Loads all rows in the SCH table whose field nFieldID match the key of an SDO being loaded
  731. template<class SCH>
  732. bool BYieldingExecuteSingleTable( int nFieldID, CResults< SCH >& Results );
  733. // Loads the specified columns for all rows in the SCH table whose field nFieldID match the key of an SDO being loaded
  734. template<class SCH>
  735. bool BYieldingExecuteSingleTable( int nFieldID, const CColumnSet &csetRead, CResults< SCH >& Results );
  736. // Functions to load rows from more than one table at a time
  737. // Loads all rows in the SCH table whose field nFieldID match the key of an SDO being loaded
  738. template<class SCH>
  739. void AddTableToQuery( int nFieldID );
  740. // Loads the specified columns for all rows in the SCH table whose field nFieldID match the key of an SDO being loaded
  741. template<class SCH>
  742. void AddTableToQuery( int nFieldID, const CColumnSet &csetRead );
  743. // Executes the mutli-table query
  744. bool BYieldingExecute();
  745. // Gets the results for a table from a multi-table query
  746. template<class SCH>
  747. bool BGetResults( int nQuery, CResults< SCH >& Results );
  748. private:
  749. CUtlVector<typename T::KeyType_t> m_vecKeys;
  750. CSQLAccess m_sqlAccess;
  751. struct Query_t
  752. {
  753. Query_t( const CColumnSet &columnSet, int nKeyCol ) : m_ColumnSet( columnSet ), m_nKeyCol( nKeyCol ) {}
  754. CColumnSet m_ColumnSet;
  755. int m_nKeyCol;
  756. };
  757. CUtlVector<Query_t> m_vecQueries;
  758. };
  759. //utility to help with iteration through a SQL load result list
  760. #define FOR_EACH_SQL_LOAD_RESULT( Results, Key, Index ) for( int Index = Results.GetFirstResultIndex( Key ); Index != Results.InvalidIndex(); Index = Results.GetNextResultIndex( Index ) )
  761. //-----------------------------------------------------------------------------
  762. // Purpose: Constructor. Initializes with the vector of objects being loaded
  763. //-----------------------------------------------------------------------------
  764. template<class T>
  765. CSDOSQLLoadHelper<T>::CSDOSQLLoadHelper( const CUtlVector<ISDO *> *vecSDOToLoad, const char *pchProfileName )
  766. : m_vecKeys( 0, vecSDOToLoad->Count() )
  767. {
  768. FOR_EACH_VEC( *vecSDOToLoad, i )
  769. {
  770. m_vecKeys.AddToTail( ( (T*)vecSDOToLoad->Element( i ) )->GetKey() );
  771. }
  772. Assert( m_vecKeys.Count() > 0 );
  773. DbgVerify( m_sqlAccess.BBeginTransaction( pchProfileName ) );
  774. }
  775. //-----------------------------------------------------------------------------
  776. // Purpose: Loads all rows in the SCH table whose field nFieldID match the
  777. // key of an SDO being loaded.
  778. //-----------------------------------------------------------------------------
  779. template<class T>
  780. template<class SCH>
  781. bool CSDOSQLLoadHelper<T>::BYieldingExecuteSingleTable( int nFieldID, CResults< SCH >& Results )
  782. {
  783. static const CColumnSet cSetRead = CColumnSet::Full<SCH>();
  784. return BYieldingExecuteSingleTable( nFieldID, cSetRead, Results );
  785. }
  786. //-----------------------------------------------------------------------------
  787. // Purpose: Loads the specified columns for all rows in the SCH table whose
  788. // field nFieldID match the key of an SDO being loaded
  789. //-----------------------------------------------------------------------------
  790. template<class T>
  791. template<class SCH>
  792. bool CSDOSQLLoadHelper<T>::BYieldingExecuteSingleTable( int nFieldID, const CColumnSet &csetRead, CResults< SCH >& Results )
  793. {
  794. AddTableToQuery<SCH>( nFieldID, csetRead );
  795. if ( !BYieldingExecute() )
  796. return false;
  797. return BGetResults<SCH>( 0, Results );
  798. }
  799. //-----------------------------------------------------------------------------
  800. // Purpose: Loads all rows in the SCH table whose field nFieldID match the key
  801. // of an SDO being loaded
  802. //-----------------------------------------------------------------------------
  803. template<class T>
  804. template<class SCH>
  805. void CSDOSQLLoadHelper<T>::AddTableToQuery( int nFieldID )
  806. {
  807. static const CColumnSet cSetRead = CColumnSet::Full<SCH>();
  808. AddTableToQuery<SCH>( nFieldID, cSetRead );
  809. }
  810. //-----------------------------------------------------------------------------
  811. // Purpose: Loads the specified columns for all rows in the SCH table whose
  812. // field nFieldID match the key of an SDO being loaded
  813. //-----------------------------------------------------------------------------
  814. template<class T>
  815. template<class SCH>
  816. void CSDOSQLLoadHelper<T>::AddTableToQuery( int nFieldID, const CColumnSet &csetRead )
  817. {
  818. Assert( csetRead.GetRecordInfo() == GSchemaFull().GetSchema( SCH::k_iTable ).GetRecordInfo() );
  819. // Bind the params
  820. FOR_EACH_VEC( m_vecKeys, i )
  821. {
  822. m_sqlAccess.AddBindParam( m_vecKeys[i] );
  823. }
  824. // Build the query
  825. CUtlString sCommand;
  826. {
  827. TSQLCmdStr sSelect;
  828. const char *pchColumnName = GSchemaFull().GetSchema( SCH::k_iTable ).GetRecordInfo()->GetColumnInfo( nFieldID ).GetName();
  829. BuildSelectStatementText( &sSelect, csetRead );
  830. sCommand.Format( "%s WHERE %s IN (%.*s)", sSelect.Access(), pchColumnName, GetInsertArgStringChars( m_vecKeys.Count() ), GetInsertArgString() );
  831. }
  832. // Execute. Because we're in a transaction this will delay to the commit
  833. DbgVerify( m_sqlAccess.BYieldingExecute( NULL, sCommand ) );
  834. m_vecQueries.AddToTail( Query_t( csetRead, nFieldID ) );
  835. }
  836. //-----------------------------------------------------------------------------
  837. // Purpose: Executes the mutli-table query
  838. //-----------------------------------------------------------------------------
  839. template<class T>
  840. bool CSDOSQLLoadHelper<T>::BYieldingExecute()
  841. {
  842. if ( 0 == m_vecKeys.Count() )
  843. {
  844. m_sqlAccess.RollbackTransaction();
  845. return false;
  846. }
  847. if ( !m_sqlAccess.BCommitTransaction() )
  848. return false;
  849. Assert( (uint32)m_vecQueries.Count() == m_sqlAccess.GetResultSetCount() );
  850. return (uint32)m_vecQueries.Count() == m_sqlAccess.GetResultSetCount();
  851. }
  852. //-----------------------------------------------------------------------------
  853. // Purpose: Gets the results for a table from a multi-table query
  854. //-----------------------------------------------------------------------------
  855. template<class T>
  856. template<class SCH>
  857. bool CSDOSQLLoadHelper<T>::BGetResults( int nQuery, CResults< SCH >& Results )
  858. {
  859. //clear any previous results
  860. Results.m_KeyToResult.Purge();
  861. Results.m_Results.Purge();
  862. IGCSQLResultSetList *pSQLResults = m_sqlAccess.GetResults();
  863. Assert( pSQLResults && nQuery >= 0 && (uint32)nQuery < pSQLResults->GetResultSetCount() && pSQLResults->GetResultSetCount() == (uint32)m_vecQueries.Count() );
  864. if ( NULL == pSQLResults || nQuery < 0 || (uint32)nQuery >= pSQLResults->GetResultSetCount() || pSQLResults->GetResultSetCount() != (uint32)m_vecQueries.Count() )
  865. return false;
  866. Assert( m_vecQueries[nQuery].m_ColumnSet.GetRecordInfo()->GetTableID() == SCH::k_iTable );
  867. if ( m_vecQueries[nQuery].m_ColumnSet.GetRecordInfo()->GetTableID() != SCH::k_iTable )
  868. return false;
  869. //copy the results from the SQL queries over to our result list
  870. Results.m_Results.EnsureCapacity( pSQLResults->GetResultSet( nQuery )->GetRowCount() );
  871. if ( !CopyResultToSchVector( pSQLResults->GetResultSet( nQuery ), m_vecQueries[nQuery].m_ColumnSet, &Results.m_Results ) )
  872. return false;
  873. // Make a map that counts maps from our results into a sorted list for fast lookup
  874. Results.m_KeyToResult.SetSize( Results.m_Results.Count() );
  875. FOR_EACH_VEC( Results.m_Results, nCurrResult )
  876. {
  877. //get our key value
  878. uint8 *pubData;
  879. uint32 cubData;
  880. if ( !Results.m_Results[ nCurrResult ].BGetField( m_vecQueries[nQuery].m_nKeyCol, &pubData, &cubData ) )
  881. return false;
  882. Assert( cubData == sizeof( T::KeyType_t ) );
  883. if ( cubData != sizeof( T::KeyType_t ) )
  884. {
  885. Results.m_KeyToResult.Purge();
  886. Results.m_Results.Purge();
  887. return false;
  888. }
  889. //setup our record
  890. Results.m_KeyToResult[ nCurrResult ].m_Key = *((T::KeyType_t *)pubData);
  891. Results.m_KeyToResult[ nCurrResult ].m_nResultIndex = nCurrResult;
  892. }
  893. //sort for binary search capabilities
  894. std::sort( Results.m_KeyToResult.begin(), Results.m_KeyToResult.end() );
  895. return true;
  896. }
  897. template< typename T >
  898. template< typename SCH >
  899. int CSDOSQLLoadHelper< T >::CResults< SCH >::GetKeyIndexRange( typename T::KeyType_t Key, int& nStart, int& nEnd ) const
  900. {
  901. //find the start of the range
  902. nStart = GetFirstResultIndex( Key );
  903. if( nStart == InvalidIndex() )
  904. {
  905. nEnd = InvalidIndex();
  906. return 0;
  907. }
  908. //expand the end as long as it lies on a key in range and with the same value
  909. for( nEnd = nStart + 1; ( nEnd < m_KeyToResult.Count() ) && ( m_KeyToResult[ nEnd ].m_Key == Key ); nEnd++ )
  910. {
  911. }
  912. //and return the resulting number of elements we found
  913. return nEnd - nStart;
  914. }
  915. template< typename T >
  916. template< typename SCH >
  917. const SCH* CSDOSQLLoadHelper< T >::CResults< SCH >::GetSingleResultForKey( typename T::KeyType_t Key ) const
  918. {
  919. int nIndex = GetFirstResultIndex( Key );
  920. 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" );
  921. return GetResultFromIndex( nIndex );
  922. }
  923. template< typename T >
  924. template< typename SCH >
  925. int CSDOSQLLoadHelper< T >::CResults< SCH >::GetFirstResultIndex( typename T::KeyType_t Key ) const
  926. {
  927. //dummy entry to compare against
  928. SKeyToResult< typename T::KeyType_t > SearchKey;
  929. SearchKey.m_Key = Key;
  930. //binary search to find our index
  931. const SKeyToResult< typename T::KeyType_t >* pMatch = std::lower_bound( m_KeyToResult.begin(), m_KeyToResult.end(), SearchKey );
  932. //see if we found a match
  933. if( ( pMatch == m_KeyToResult.end() ) || ( pMatch->m_Key != Key ) )
  934. return InvalidIndex();
  935. return ( pMatch - m_KeyToResult.begin() );
  936. }
  937. template< typename T >
  938. template< typename SCH >
  939. int CSDOSQLLoadHelper< T >::CResults< SCH >::GetNextResultIndex( int nOldResultIndex ) const
  940. {
  941. //handle out of range elements. Either invalid, or the last element or beyond in our array
  942. if( ( nOldResultIndex < 0 ) || ( nOldResultIndex + 1 >= m_KeyToResult.Count() ) )
  943. return InvalidIndex();
  944. //see if we are less than the next value in the list. If so, we aren't equal and need to stop iteration
  945. if( m_KeyToResult[ nOldResultIndex ] < m_KeyToResult[ nOldResultIndex + 1 ] )
  946. return InvalidIndex();
  947. //same key for the next element, so return a match
  948. return nOldResultIndex + 1;
  949. }
  950. template< typename T >
  951. template< typename SCH >
  952. const SCH* CSDOSQLLoadHelper< T >::CResults< SCH >::GetResultFromIndex( int nIndex ) const
  953. {
  954. //ensure that the index is in range
  955. if( ( nIndex < 0 ) || ( nIndex >= m_KeyToResult.Count() ) )
  956. return NULL;
  957. return &( m_Results[ m_KeyToResult[ nIndex ].m_nResultIndex ] );
  958. }
  959. } // namespace GCSDK
  960. #endif // SDOCACHE_H