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.

499 lines
20 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Base class for transactions that modify a CGCSharedObjectCache and the database
  4. //
  5. //=============================================================================
  6. #ifndef SHAREDOBJECTTRANSACTION_H
  7. #define SHAREDOBJECTTRANSACTION_H
  8. #ifdef _WIN32
  9. #pragma once
  10. #endif
  11. #include <functional>
  12. namespace GCSDK
  13. {
  14. template < typename TSharedObject >
  15. struct SharedObjectContainsAuditEntryType
  16. {
  17. typedef char t_Yes[1];
  18. typedef char t_No[2];
  19. template < typename T >
  20. static t_Yes& Test( typename T::CAuditEntry * );
  21. template < typename T >
  22. static t_No& Test( ... );
  23. enum { kValue = sizeof( Test<TSharedObject>( NULL ) ) == sizeof( t_Yes ) };
  24. };
  25. //-----------------------------------------------------------------------------
  26. // Purpose: Let's stop writing transactional code by hand everywhere!
  27. //
  28. // Core usage:
  29. //
  30. // - make a new instance, either starting a new SQL transaction or hooking
  31. // into an existing open transaction.
  32. //
  33. // - call some combination of AddNewObject/RemoveObject to add newly-allocated
  34. // objects or remove existing objects. If the types of objects being added/
  35. // removed contain a linked audit data class, you're required to pass in
  36. // a filled-out instance with the add/remove request.
  37. //
  38. // - modify some existing objects. You can either call ModifyObject directly
  39. // or call one of the helper functions/macros (ie., ModifyObjectSch).
  40. // Whatever changes get made here won't be visible anywhere outside this
  41. // transaction object until a commit succeeds.
  42. //
  43. // - try to do a commit. If this succeeds, everything worked and memory has
  44. // been updated to reflect DB changes (if any). If this fails, or if the
  45. // transaction object gets destroyed with an open transaction, all queued
  46. // SQL work will be dropped and no potential memory changes will happen.
  47. //
  48. // That was too long. What's a short version?:
  49. //
  50. // {
  51. // CSharedObjectTransactionEx transaction( pSomeLockedSOCache, "Sample Tool Transaction" );
  52. // transaction.RemoveObject( pToolItem, CEconItem::CAuditEntry( ... ) );
  53. // transaction.RemoveObject( pToolTargetItem, CEconItem::CAuditEntry( ... ) );
  54. // transaction.AddNewObject( pNewResultItem, CEconItem::CAuditEntry( ... ) );
  55. // transaction.ModifyObjectSch( pEconGameAccount, unNumToolsUsed, pEconGameAccount->Obj().m_pEconGameAccount + 1 );
  56. // Verify( transaction.BYieldingCommit() );
  57. // }
  58. //
  59. // Guarantees this class makes:
  60. //
  61. // - if the initial state is correct and client code isn't malicious, a
  62. // lock on the SO cache passed in will be maintained for the lifetime
  63. // of the class.
  64. //
  65. // - externally, no changes will be visible on the GC or on cients until a
  66. // commit attempt takes place. If the commit failures, no changes will
  67. // take place in memory. If the commit succeeds, all memory changes will
  68. // become visible "simultaneously" (no yields, but not atomic from a
  69. // threading perspective.
  70. //
  71. // - this class will do the minimum amount of work possible to guarantee
  72. // correct behavior. If you don't touch any networked objects, no network
  73. // updates will be sent. If you don't touch any DB-backed objects, no DB
  74. // work will be done.
  75. //-----------------------------------------------------------------------------
  76. class CSharedObjectTransactionEx
  77. {
  78. public:
  79. CSharedObjectTransactionEx( CGCSharedObjectCache *pLockedSOCache, const char *pszTransactionName );
  80. ~CSharedObjectTransactionEx();
  81. // AddNewObject:
  82. // Add a new object to this user's SO cache. If the type of this object supports audit entries, you're
  83. // required to pass in a CAuditEntry of the appropriate type (ie., CEconItem::CAuditEntry). If your type
  84. // doesn't support audit entries, and you're really sure that not auditing in the correct behavior, you
  85. // call the single-argument version below. In both cases, it's illegal to pass in an object that's
  86. // a raw CSharedObject pointer because then this code doesn't know what type of audit data to look for.
  87. //
  88. // It's possible to set up these functions using SFINAE so that the compiler will only see the appropriate
  89. // version, but this way produces prettier error messages.
  90. // Add an object to this user's cache, conditional on the SQL transaction succeeding, including writing an
  91. // audit entry to SQL explaining where this object came from.
  92. template < typename TSharedObject >
  93. void AddNewObject( TSharedObject *pObject, const typename TSharedObject::CAuditEntry& audit )
  94. {
  95. COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSharedObject, CSharedObject>::kValue) );
  96. COMPILE_TIME_ASSERT( SharedObjectContainsAuditEntryType<TSharedObject>::kValue );
  97. m_bTransactionBuildSuccess &= BAddNewObjectInternal( pObject );
  98. m_bTransactionBuildSuccess &= audit.BAddAuditEntryToTransaction( *m_pSQLAccess, pObject );
  99. }
  100. // Add an object to this user's cache, conditional on the SQL transaction succeeding, but don't write any
  101. // audit data. In general, this is probably the wrong thing to do and you want to be making sure this object
  102. // type supports writing audit data and then writing it.
  103. template < typename TSharedObject >
  104. void AddNewObject( TSharedObject *pObject )
  105. {
  106. COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSharedObject, CSharedObject>::kValue) );
  107. COMPILE_TIME_ASSERT( !SharedObjectContainsAuditEntryType<TSharedObject>::kValue );
  108. m_bTransactionBuildSuccess &= BAddNewObjectInternal( pObject );
  109. }
  110. // This is a helper class purely to help VC will template type deduction. The real signature we want for the
  111. // base ModifyObject function is:
  112. //
  113. // template < typename TSharedObject >
  114. // void ModifyObject( TSharedObject *pObject, const std::function< bool( CSQLAccess&, CSharedObjectDirtyList&, TSharedObject * ) >& funcModifyAndAudit )
  115. //
  116. // ...but if we do do that, VC will complain that it doesn't know how to deduce the type for TSharedObject
  117. // because of the second parameter. Instead, we make it deduce the type based on the first parameter, and then
  118. // feed that type into this helper class to get the type for the second parameter.
  119. template < typename TSharedObject >
  120. struct CSharedObjectModifyAndAuditFunction
  121. {
  122. typedef std::function< bool( CSQLAccess&, CSharedObjectDirtyList&, TSharedObject * ) > ModifyFunctionType;
  123. };
  124. // ModifyObject: takes in
  125. template < typename TSharedObject >
  126. void ModifyObject( TSharedObject *pObject, const typename CSharedObjectModifyAndAuditFunction<TSharedObject>::ModifyFunctionType& funcModifyAndAudit )
  127. {
  128. COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSharedObject, CSharedObject>::kValue) );
  129. CSharedObject *pWritableObject = NULL;
  130. m_bTransactionBuildSuccess &= BTrackModifiedObjectInternal( pObject, &pWritableObject );
  131. AssertMsg( !m_bTransactionBuildSuccess || pWritableObject != NULL, "Cannot be tracking state for an object but not having a writable version!" );
  132. m_bTransactionBuildSuccess &= funcModifyAndAudit( *m_pSQLAccess, m_SODirtyList, assert_cast<TSharedObject *>( pWritableObject ) );
  133. }
  134. // ModifyObjectL() is a helper macro designed to behave like a function. It takes as parameters the original
  135. // object you want to modify and the code you want to run on it, expressed as a lambda.
  136. #define ModifyObjectL( obj_, modifyfunc_ ) \
  137. ModifyObject( obj_, [=] ( CSQLAccess& sqlAccess, CSharedObjectDirtyList& SODirtyList, decltype( obj_ ) pWritableObject ) -> bool { modifyfunc_ } )
  138. // ModifyObjectSch() is a helper macro designed to behave like a function. It takes as parameters the original
  139. // object you want to modify, an identifier for the field you want to change, and the new value you want to
  140. // change it to. Ex.:
  141. //
  142. // transaction.ModifyObjectSch( pLockedSOCache->GetGameAccount(), // type of internal object is used to look up field IDs
  143. // unNextHalloweenGiftTime, // turns into "(obj).m_unNextHalloweenGiftTime" and "(type)::k_iField_unNextHalloweenGiftTime"
  144. // CRTime::RTime32DateAdd( CRTime::RTime32TimeCur(), tf_halloween_min_minutes_between_drops_per_player.GetInt(), k_ETimeUnitMinute ) );
  145. #define ModifyObjectSch( obj_, field_, newvalue_ ) \
  146. ModifyObjectL( obj_, \
  147. { \
  148. pWritableObject->Obj().m_ ## field_ = (newvalue_); \
  149. SODirtyList.DirtyObjectField( pWritableObject, std::remove_reference< decltype( pWritableObject->Obj() ) >::type::k_iField_ ## field_ ); \
  150. return true; \
  151. } )
  152. // ModifyObjectProto() is a helper macro designed to behave like a function. It takes as parameters the original
  153. // object you want to modify, the field name in the message or the field you want to change, the identifier field
  154. // ID (will be identical except for case/underscores), and the new value. Ex.:
  155. //
  156. // soTrans.ModifyObjectProto( pGameAccountClient, // type of internal object is used to look up field IDs
  157. // preview_item_def, // turns into "(obj)->set_preview_item_def"
  158. // PreviewItemDef, // turns into "(type)::kPreviewItemDefFieldNumber"
  159. // 0 );
  160. #define ModifyObjectProto( obj_, fieldfunc_, fieldname_, newvalue_ ) \
  161. ModifyObjectL( obj_, \
  162. { \
  163. pWritableObject->Obj().set_ ## fieldfunc_( newvalue_ ); \
  164. SODirtyList.DirtyObjectField( pWritableObject, std::remove_reference< decltype( pWritableObject->Obj() ) >::type::k ## fieldname_ ## FieldNumber ); \
  165. return true; \
  166. } )
  167. // RemoveObject
  168. template < typename TSharedObject >
  169. void RemoveObject( TSharedObject *pObject, const typename TSharedObject::CAuditEntry& audit )
  170. {
  171. COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSharedObject, CSharedObject>::kValue) );
  172. COMPILE_TIME_ASSERT( SharedObjectContainsAuditEntryType<TSharedObject>::kValue );
  173. m_bTransactionBuildSuccess &= BRemoveObjectInternal( pObject );
  174. m_bTransactionBuildSuccess &= audit.BAddAuditEntryToTransaction( *m_pSQLAccess, pObject );
  175. }
  176. template < typename TSharedObject >
  177. void RemoveObject( CSharedObject *pObject )
  178. {
  179. COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSharedObject, CSharedObject>::kValue) );
  180. COMPILE_TIME_ASSERT( !SharedObjectContainsAuditEntryType<TSharedObject>::kValue );
  181. m_bTransactionBuildSuccess &= BRemoveObjectInternal( pObject );
  182. }
  183. template < class TSchType >
  184. void AddSQLRecord( const TSchType& sch )
  185. {
  186. // We can't test for all types here because there's no compile-time list. This is
  187. // mostly to demonstrate "seriously please don't call this with types that have
  188. // CSharedObject wrappers".
  189. COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSchType, CSchItem>::kValue) );
  190. COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSchType, CSchItemAudit>::kValue) );
  191. COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSchType, CSchGameAccount>::kValue) );
  192. // We're about to call a function with "Yielding" in the name and we aren't in a
  193. // function with "Yielding" in the name, but we *are* in a transaction so we know
  194. // we don't yield. We verify that assumption here.
  195. DO_NOT_YIELD_THIS_SCOPE();
  196. // Queue up the work for SQL as long as we're in a good state to do so.
  197. m_bTransactionBuildSuccess &= BIsValidInternalState()
  198. ? m_pSQLAccess->BYieldingInsertRecord( &sch )
  199. : false;
  200. }
  201. template < class TSchType >
  202. void AddOrUpdateSQLRecord( TSchType& sch )
  203. {
  204. // We can't test for all types here because there's no compile-time list. This is
  205. // mostly to demonstrate "seriously please don't call this with types that have
  206. // CSharedObject wrappers".
  207. COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSchType, CSchItem>::kValue) );
  208. COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSchType, CSchItemAudit>::kValue) );
  209. COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSchType, CSchGameAccount>::kValue) );
  210. // We're about to call a function with "Yielding" in the name and we aren't in a
  211. // function with "Yielding" in the name, but we *are* in a transaction so we know
  212. // we don't yield. We verify that assumption here.
  213. DO_NOT_YIELD_THIS_SCOPE();
  214. // Queue up the work for SQL as long as we're in a good state to do so.
  215. m_bTransactionBuildSuccess &= BIsValidInternalState()
  216. ? m_pSQLAccess->BYieldingInsertOrUpdateOnPK( &sch )
  217. : false;
  218. }
  219. // This is meant purely for interop with the SQL message queue and even then only as a temporary
  220. // measure until we have a CSQLTransaction object we can pass around instead.
  221. CSQLAccess& GetSQLTransactionForSQLMsgQueue() { return *m_pSQLAccess; }
  222. // slow! but fine for current uses
  223. template < class TSharedObject >
  224. const TSharedObject *FindTypedSharedObject( const CSharedObject &soIndex ) const
  225. {
  226. return assert_cast<TSharedObject *>( InternalFindSharedObject( pSOCache, soIndex ) );
  227. }
  228. // Take all the work we queued up for SQL and try to commit it to the DB. If that works, take all
  229. // of our memory changes and copy them over. From the outside, this will either move *all* memory
  230. // changes to our cache over at once, or not touch any in-memory structures at all.
  231. MUST_CHECK_RETURN bool BYieldingCommit();
  232. // Cancel this transaction completely -- this will empty the queue of whatever SQL work we may have
  233. // done and also free up the memory we used to track modified object state, etc. Once this function
  234. // gets called, it's illegal to call any other functions on this transaction object except the
  235. // destructor.
  236. void Rollback();
  237. private:
  238. // State validation. Non-const because may set internal error state.
  239. bool BIsValidInternalState();
  240. bool BIsValidInput( const CSharedObject *pObject );
  241. const char *GetInternalTransactionDesc() const { return m_pSQLAccess->PchTransactionName(); }
  242. template < typename tCommitInfo >
  243. const tCommitInfo *InternalFindCommitInfo( const CSharedObject *pObject, const CUtlVector<tCommitInfo>& vec ) const
  244. {
  245. FOR_EACH_VEC( vec, i )
  246. {
  247. if ( vec[i].m_pObject == pObject )
  248. return &vec[i];
  249. }
  250. return NULL;
  251. }
  252. const CSharedObject *InternalFindSharedObject( CGCSharedObjectCache *pSOCache, const CSharedObject& soIndex ) const;
  253. // Will return NULL if pre-commit operations/checks were successful, or a pointer to a descriptive error string if
  254. // pre-commit failed.
  255. const char *InternalPreCommit();
  256. bool BAddNewObjectInternal( CSharedObject *pObject );
  257. bool BTrackModifiedObjectInternal( CSharedObject *pObject, CSharedObject **out_ppWritableObject );
  258. bool BRemoveObjectInternal( CSharedObject *pObject );
  259. friend class CTrustedHelper_OutputAndSetErrorState;
  260. void SetErrorState() { m_bTransactionBuildSuccess = false; }
  261. private:
  262. CGCSharedObjectCache *m_pLockedSOCache; // we don't do any locking ourself, but verify that the lock is held during construction/modification
  263. CSharedObjectDirtyList m_SODirtyList;
  264. CSQLAccess *m_pSQLAccess; // our access to SQL -- may point to our inline instance or may point to an external object if we're hitching on an already-existing transaction
  265. bool m_bTransactionBuildSuccess;
  266. CSQLAccess m_sqlAccessInternal;
  267. struct CreateOrDestroyCommitInfo_t
  268. {
  269. CreateOrDestroyCommitInfo_t( CSharedObject *pObject ) : m_pObject( pObject ) { Assert( m_pObject ); }
  270. CSharedObject *m_pObject;
  271. };
  272. struct ModifyCommitInfo_t
  273. {
  274. ModifyCommitInfo_t( CSharedObject *pWriteableObject, CSharedObject *pOriginalCopy )
  275. : m_pWriteableObject( pWriteableObject )
  276. , m_pObject( pOriginalCopy )
  277. {
  278. }
  279. CSharedObject *m_pWriteableObject; // scratch/memory-writable copy while transaction is open
  280. CSharedObject *m_pObject; // original copy
  281. };
  282. CUtlVector<CreateOrDestroyCommitInfo_t> m_vecObjects_Added;
  283. CUtlVector<CreateOrDestroyCommitInfo_t> m_vecObjects_Removed;
  284. CUtlVector<ModifyCommitInfo_t> m_vecObjects_Modified;
  285. };
  286. class CSharedObjectTransaction
  287. {
  288. public:
  289. /**
  290. * Constructor that will begin a transaction
  291. * @param sqlAccess
  292. * @param pName
  293. */
  294. CSharedObjectTransaction( CSQLAccess &sqlAccess, const char *pName );
  295. /**
  296. * Destructor
  297. */
  298. ~CSharedObjectTransaction();
  299. /**
  300. * Adds an object that exists in the given CGCSharedObjectCache to be managed in this transaction.
  301. * Call this before making any modifications to the object
  302. * @param pSOCache the owner CGCSharedObjectCache
  303. * @param pObject the object that will be modified
  304. */
  305. void AddManagedObject( CGCSharedObjectCache *pSOCache, CSharedObject *pObject );
  306. /**
  307. * Adds a brand new object to the given CGCSharedObjectCache
  308. * @param pSOCache the owner CGCSharedObjectCache
  309. * @param pObject the newly created object
  310. */
  311. void AddNewObject( CGCSharedObjectCache *pSOCache, CSharedObject *pObject );
  312. /**
  313. * Removes an existing object from the CGCSharedObjectCache
  314. * @param pSOCache the owner CGCSharedObjectCache
  315. * @param pObject the object to be removed from the CGCSharedObjectCache
  316. */
  317. void RemoveObject( CGCSharedObjectCache *pSOCache, CSharedObject *pObject );
  318. /**
  319. * Marks in the transaction that the object was modified. The object must have been previously added via
  320. * the AddManagedObject() call in order for the object to be marked dirty. If the object is new to the
  321. * CGCSharedObjectCache, then calling this will return false (which is not necessarily an error)
  322. *
  323. * @param pObject the object that will be modified
  324. * @param unFieldIdx the field that was changed
  325. */
  326. void ModifiedObject( CGCSharedObjectCache *pSOCache, CSharedObject *pObject, uint32 unFieldIdx );
  327. /**
  328. * @param pSOCache
  329. * @param soIndex
  330. * @return the CSharedObject that matches either in the CGCSharedObjectCache or to be added
  331. */
  332. template < class T >
  333. T *FindTypedSharedObject( CGCSharedObjectCache *pSOCache, const CSharedObject &soIndex )
  334. {
  335. return assert_cast<T *>( FindSharedObject( pSOCache, soIndex ) );
  336. }
  337. /**
  338. * Rolls back any changes made to the objects in-memory and in the database
  339. *
  340. * This function should not be made virtual -- it's called from within the destructor.
  341. */
  342. void Rollback();
  343. /**
  344. * Commits any changes to the database and also to memory
  345. * @return true if successful, false otherwise
  346. */
  347. bool BYieldingCommit( bool bAllowEmpty = false );
  348. /**
  349. * @return GCSDK::CSQLAccess associated with this transaction
  350. */
  351. CSQLAccess &GetSQLAccess() { return m_sqlAccess; }
  352. /**
  353. * Fetch name of transaction for debugging purposes
  354. * @return the string passed to the constructor
  355. */
  356. const char *PchName() const;
  357. private:
  358. /**
  359. * @param pSOCache
  360. * @param soIndex
  361. * @return the CSharedObject that matches either in the list of currently-modified objects or the list
  362. * or of new objects we added; this will not search in the base SO cache
  363. */
  364. CSharedObject *FindSharedObject( CGCSharedObjectCache *pSOCache, const CSharedObject &soIndex );
  365. /**
  366. * Reverts all in-memory modifications and deletes all newly created objects.
  367. *
  368. * This function should not be made virtual -- it's called from within the destructor.
  369. */
  370. void Undo();
  371. /**
  372. * Set an error string to describe an error we encountered building this transaction. Setting this
  373. * will cause the transaction to fail.
  374. */
  375. void SetError( const char *pszNewError )
  376. {
  377. AssertMsg( pszNewError && ( pszNewError[0] != '\0' ), "Invalid NULL/empty error set in CSharedObjectTransaction::SetError()! This will have the effect of clearing the error state, which is unsupported." );
  378. m_sErrorDesc = pszNewError;
  379. }
  380. /**
  381. * Clear our error string if we have one. This will allow transactions to succeed and so is only intended
  382. * to be done when the transaction itself has either completed (no either to begin with) or emptied (clean
  383. * slate).
  384. */
  385. void ClearError()
  386. {
  387. Assert( m_vecObjects_Added.Count() == 0 );
  388. Assert( m_vecObjects_Removed.Count() == 0 );
  389. Assert( m_vecObjects_Modified.Count() == 0 );
  390. m_sErrorDesc.Clear();
  391. }
  392. /**
  393. * Get access to the string describing what, if any, the last error we encountered was. Will return
  394. * NULL if no errors have been encountered.
  395. */
  396. const char *GetError() const
  397. {
  398. return m_sErrorDesc.IsEmpty() ? NULL : m_sErrorDesc.String();
  399. }
  400. struct undoinfo_t
  401. {
  402. CSharedObject *pObject;
  403. CGCSharedObjectCache *pSOCache;
  404. CSharedObject *pOriginalCopy;
  405. bool operator==( const undoinfo_t& other ) const { return other.pObject == pObject && other.pSOCache == pSOCache && other.pOriginalCopy == pOriginalCopy; }
  406. };
  407. // Wraps the common check to make sure these pointers are valid and that the cache is locked. This is non-const
  408. // because it can call SetError().
  409. bool AssertValidInput( const CGCSharedObjectCache *pSOCache, const CSharedObject *pObject, const char *pszContext );
  410. // Finds the object in the given vector (using simple pointer compare)
  411. undoinfo_t *FindObjectInVector( const CSharedObject *pObject, CUtlVector<undoinfo_t> &vec ) const;
  412. // variables
  413. CUtlVector< undoinfo_t > m_vecObjects_Added;
  414. CUtlVector< undoinfo_t > m_vecObjects_Removed;
  415. CUtlVector< undoinfo_t > m_vecObjects_Modified;
  416. CSQLAccess &m_sqlAccess;
  417. // internal error state
  418. CUtlString m_sErrorDesc; // will be non-empty if we've encountered an error at some point building this transaction
  419. }; // class CSharedObjectTransaction
  420. }; // namespace GCSDK
  421. #endif // SHAREDOBJECTTRANSACTION_H