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.

834 lines
32 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #ifndef GCBASE_H
  7. #define GCBASE_H
  8. #ifdef _WIN32
  9. #pragma once
  10. #endif
  11. #include "gamecoordinator/igamecoordinator.h"
  12. #include "gamecoordinator/igamecoordinatorhost.h"
  13. #include "tier1/utlallocation.h"
  14. #include "gcmsg.h"
  15. #include "jobmgr.h"
  16. #include "tier1/thash.h"
  17. #include "tier1/UtlSortVector.h"
  18. #include "http.h"
  19. #include "language.h"
  20. #include "accountdetails.h"
  21. #include "gcsession.h"
  22. class CGCMsgGetSystemStatsResponse;
  23. extern EUniverse GetUniverse();
  24. const int16 k_nLockTypeLobby = 3;
  25. const int16 k_nLockTypeParty = 2;
  26. const int16 k_nLockTypeIndividual = 1;
  27. const int16 k_nLockTypeGameServer = 0;
  28. const int16 k_nLockTypeGroupIDGeneration = -1;
  29. // For one-off locks of specific resources. The specific game controls the subtype ordering.
  30. const int16 k_nLockTypeGameMisc = -10;
  31. namespace GCSDK
  32. {
  33. class CGCSession;
  34. class CGCUserSession;
  35. class CGCGSSession;
  36. class CGCSharedObjectCache;
  37. class CSharedObject;
  38. class CAccountDetails;
  39. class CScopedSteamIDLock;
  40. struct PackageLicense_t
  41. {
  42. uint32 m_unPackageID;
  43. RTime32 m_rtimeCreated;
  44. };
  45. #define SERVER_KEY_HASH 0x5a4f4944u
  46. class CGCBase : public IGameCoordinator
  47. {
  48. public:
  49. CGCBase( );
  50. virtual ~CGCBase();
  51. // Hooks to extend the base behaviors of the IGameCoordinator interface
  52. virtual bool OnInit() { return true; }
  53. virtual bool OnMainLoopOncePerFrame( CLimitTimer &limitTimer ) { return false; }
  54. virtual bool OnMainLoopUntilFrameCompletion( CLimitTimer &limitTimer ) { return false; }
  55. virtual void OnUninit() {}
  56. // returns true if this function handled the message
  57. virtual bool OnMessageFromClient( const CSteamID & senderID, uint32 unMsgType, void *pubData, uint32 cubData ) { return false; }
  58. virtual void OnValidate( CValidator &validator, const char *pchName ) {}
  59. virtual const char *LocalizeToken( const char *pchToken, ELanguage eLanguage, bool bReturnTokenIfNotFound = true ) { return bReturnTokenIfNotFound ? pchToken : NULL; }
  60. virtual void YldOnAccountPhoneVerificationChange( CSteamID steamID ) {}
  61. virtual void YldOnAccountTwoFactorChange( CSteamID steamID ) {}
  62. /// The main loop calls Run() on game servers and user sessions until a certain amount of time
  63. /// is expired. Thus the user list is iterated, but the iteration is amortized over multiple frames.
  64. /// this function is called when a sweep of the user sessions ends and a new one is about to begin.
  65. virtual void FinishedMainLoopUserSweep();
  66. // Life cycle management functions
  67. // Called to do any yielding initialization work before reporting as fully operational
  68. virtual bool BYieldingFinishStartup() = 0;
  69. // Called to do any yielding work immediately after becoming full operational
  70. virtual bool BYieldingPostStartup() = 0;
  71. // Call to report that we're fully operational
  72. void SetStartupComplete( bool bSuccess );
  73. // Called to do any yielding work before reporting as ready to shutdown
  74. virtual void YieldingGracefulShutdown() = 0;
  75. virtual CGCUserSession *CreateUserSession( const CSteamID & steamID, CGCSharedObjectCache *pSOCache ) const;
  76. virtual CGCGSSession *CreateGSSession( const CSteamID & steamID, CGCSharedObjectCache *pSOCache, uint32 unServerAddr, uint16 usServerPort ) const;
  77. virtual void YieldingSessionStartPlaying( CGCUserSession *pSession ) {}
  78. virtual void YieldingSessionStopPlaying( CGCUserSession *pSession ) {}
  79. virtual void YieldingSessionStartServer( CGCGSSession *pSession ) {}
  80. virtual void YieldingSessionStopServer( CGCGSSession *pSession ) {}
  81. virtual void YieldingSOCacheLoaded( CGCSharedObjectCache *pSOCache );
  82. virtual void UpdateGSSessionAddress( CGCGSSession *pSession, uint32 unServerAddr, uint16 usServerPort );
  83. virtual void YieldingPreTestSetup() {}
  84. // cache management
  85. CGCSharedObjectCache *YieldingGetLockedSOCache( const CSteamID &steamID, const char *pszFilename, int nLineNum );
  86. CGCSharedObjectCache *YieldingFindOrLoadSOCache( const CSteamID &steamID );
  87. CGCSharedObjectCache *FindSOCache( const CSteamID & steamID ); // non-yielding, but may return NULL if the cache exists but is not loaded
  88. bool UnloadUnusedCaches( uint32 unMaxCacheCount, CLimitTimer *pLimitTimer = NULL );
  89. void YieldingReloadCache( CGCSharedObjectCache *pSOCache );
  90. virtual CGCSharedObjectCache *CreateSOCache( const CSteamID &steamID );
  91. void FlushInventoryCache( AccountID_t unAccountID );
  92. CGCUserSession *YieldingGetLockedUserSession( const CSteamID & steamID, const char *pszFilename, int nLineNum );
  93. CGCUserSession *FindUserSession( const CSteamID & steamID ) const;
  94. bool BUserSessionPending( const CSteamID & steamID ) const;
  95. CGCGSSession *YieldingGetLockedGSSession( const CSteamID & steamID, const char *pszFilename, int nLineNum );
  96. CGCGSSession *YieldingFindOrCreateGSSession( const CSteamID & steamID, uint32 unServerAddr, uint16 usServerPort, const uint8 *pubVarData, uint32 cubVarData );
  97. CGCGSSession *FindGSSession( const CSteamID & steamID ) const;
  98. /// Lookup the user or GS session, depending on whether the supplied
  99. /// Steam ID is an individual or gameserver ID
  100. CGCSession *FindUserOrGSSession( const CSteamID & steamID ) const;
  101. CGCSession *YieldingRequestSession( const CSteamID & steamID );
  102. bool BYieldingIsOnline( const CSteamID & steamID );
  103. bool BSendWebApiRegistration();
  104. // Will return true, indicating the request was sent, a response was received, and the response had a valid
  105. // status code, or false, indicating that there was a transport-layer problem -- the request timed out, or
  106. // a response came back with an invalid status code, etc. All "false" return values are meant to indicate
  107. // temporary errors.
  108. bool BYieldingSendHTTPRequest( const CHTTPRequest *pRequest, CHTTPResponse *pResponse );
  109. // Possible return values:
  110. // k_EResultOK -- everything went fine and pkvResponse has been populated.
  111. // k_EResultTimeout -- there was a temporary/transport-layer problem (see "BYieldingSendHTTPRequest").
  112. // These are temporary errors, or programming errors on our side.
  113. // k_EResultRemoteCallFailed -- we didn't timeout but we got a bad, unparseable, or otherwise invalid response
  114. // back and so we don't have data we can use.
  115. // k_EResultFail -- something has gone catastrophically wrong and no forward progress can be
  116. // expected to be made. May or may not ever be returned.
  117. EResult YieldingSendHTTPRequestKV( const CHTTPRequest *pRequest, KeyValues *pkvResponse );
  118. int GetSOCacheCount() const;
  119. bool IsSOCached( const CSharedObject *pObj, uint32 nTypeID ) const;
  120. int GetUserSessionCount() const;
  121. int GetGSSessionCount() const;
  122. void SetIsShuttingDown();
  123. bool GetIsShuttingDown() const { return m_bIsShuttingDown; }
  124. // Iterate over sessions
  125. // WARNING: Don't yield between GetFirst*/GetNext*. Use caution when using
  126. // these any time you might have tens of thousands of sessions to iterate through.
  127. CGCUserSession **GetFirstUserSession() { return m_hashUserSessions.PvRecordFirst(); }
  128. CGCUserSession **GetNextUserSession( CGCUserSession **pUserSession ) { return m_hashUserSessions.PvRecordNext( pUserSession ); }
  129. CGCGSSession **GetFirstGSSession() { return m_hashGSSessions.PvRecordFirst(); }
  130. CGCGSSession **GetNextGSSession( CGCGSSession **pGSSession ) { return m_hashGSSessions.PvRecordNext( pGSSession ); }
  131. AppId_t GetAppID() const { return m_unAppID; }
  132. bool BIsStartupComplete() const { return m_bStartupComplete; }
  133. CJobMgr &GetJobMgr() { return m_JobMgr; }
  134. CSteamID YieldingGuessSteamIDFromInput( const char *pchInput );
  135. bool BYieldingRecordSupportAction( const CSteamID & actorID, const CSteamID & targetID, const char *pchData, const char *pchNote );
  136. void PostAlert( EAlertType eAlertType, bool bIsCritical, const char *pchAlertText, const CUtlVector< CUtlString > *pvecExtendedInfo = NULL, bool bAlsoSpew = true );
  137. const CAccountDetails *YieldingGetAccountDetails( const CSteamID & steamID, bool bForceReload = false );
  138. const char *YieldingGetPersonaName( const CSteamID & steamID, const char *pchUnknownName = NULL );
  139. void PreloadPersonaName( const CSteamID & steamID );
  140. void ClearCachedPersonaName( const CSteamID & steamID );
  141. bool BYieldingGetAccountLicenses( const CSteamID & steamID, CUtlVector< PackageLicense_t > & vecPackages );
  142. bool BYieldingLookupAccount( EAccountFindType eFindType, const char *pchInput, CUtlVector< CSteamID > *prSteamIDs );
  143. bool BYieldingAddFreeLicense( const CSteamID & steamID, uint32 unPackageID, uint32 unIPPublic = 0, const char *pchStoreCountryCode = NULL );
  144. int YieldingGrantGuestPass( const CSteamID & steamID, uint32 unPackageID, uint32 unPassesToGrant = 1, int32 nDaysToExpiration = -1 );
  145. bool BSendGCMsgToClient( const CSteamID & steamIDTarget, const CGCMsgBase& msg );
  146. bool BSendGCMsgToClient( const CSteamID & steamIDTarget, const CProtoBufMsgBase& msg );
  147. //sends a message to the system (GCH or GC.exe)
  148. bool BSendSystemMessage( const CGCMsgBase& msg, uint32 *pcubSent = NULL );
  149. bool BSendSystemMessage( const CProtoBufMsgBase& msg, uint32 *pcubSent = NULL );
  150. bool BSendSystemMessage( const ::google::protobuf::Message &msgOut, MsgType_t eSendMsg );
  151. //utilities that will send a message and then wait for the specified reply. This will return false if no reply is sent in the default timeout, or the message or status
  152. //don't match what is expected. This can only be called from within a job
  153. bool BYldSendMessageAndGetReply( const CSteamID &steamIDTarget, CProtoBufMsgBase &msgOut, CProtoBufMsgBase *pMsgIn, MsgType_t eMsg );
  154. //bool BYldSendGCMessageAndGetReply( int32 nGCDirIndex, CProtoBufMsgBase &msgOut, CProtoBufMsgBase *pMsgIn, MsgType_t eMsg );
  155. bool BYldSendSystemMessageAndGetReply( CGCMsgBase &msgOut, CGCMsgBase *pMsgIn, MsgType_t eMsg );
  156. bool BYldSendSystemMessageAndGetReply( CProtoBufMsgBase &msgOut, CProtoBufMsgBase *pMsgIn, MsgType_t eMsg );
  157. bool BYldSendSystemMessageAndGetReply( const ::google::protobuf::Message &msgSend, MsgType_t eSendMsg, ::google::protobuf::Message *pMsgResponse, MsgType_t eRespondMsg );
  158. bool BReplyToMessage( CGCMsgBase &msgOut, const CGCMsgBase &msgIn );
  159. bool BReplyToMessage( CProtoBufMsgBase &msgOut, const CProtoBufMsgBase &msgIn );
  160. //sending messages to clients or as responses with pre-serialized bodies so that the work to generate the message and body doesn't have to be done multiple times
  161. bool BSendGCMsgToClientWithPreSerializedBody( const CSteamID & steamIDTarget, MsgType_t eMsgType, const CMsgProtoBufHeader& hdr, const byte *pubBody, uint32 cubBody ) const;
  162. bool BSendGCMsgToSystemWithPreSerializedBody( MsgType_t eMsgType, const CMsgProtoBufHeader& hdr, const byte *pubBody, uint32 cubBody ) const;
  163. bool BReplyToMessageWithPreSerializedBody( MsgType_t eMsgType, const CProtoBufMsgBase &msgIn, const byte *pubBody, uint32 cubBody ) const;
  164. const char *GetPath() const { return m_sPath; }
  165. virtual const char *GetSteamAPIKey();
  166. void QueueStartPlaying( const CSteamID & steamID, const CSteamID & gsSteamID, uint32 unServerAddr, uint16 usServerPort, const uint8 *pubVarData, uint32 cubVarData );
  167. void YieldingExecuteNextStartPlaying();
  168. bool BIsInLogonSurge() const { return m_nLogonSurgeFramesRemaining > 0; }
  169. /// Are we too busy to process any "low service level guarantee" WebAPI jobs?
  170. bool BShouldThrottleLowServiceLevelWebAPIJobs() const;
  171. /// Removes the entry in the start playing queue for the specified
  172. /// Steam ID, if one exists. Returns true if it was found
  173. /// and removed, false if no entry existed
  174. bool BRemoveStartPlayingQueueEntry( const CSteamID & steamID );
  175. void YieldingStopPlaying( const CSteamID & steamID );
  176. void YieldingStopGameserver( const CSteamID & steamID );
  177. bool BIsSOCacheBeingLoaded( const CSteamID & steamID ) const { return m_rbtreeSOCachesBeingLoaded.Find( steamID ) != m_rbtreeSOCachesBeingLoaded.InvalidIndex(); }
  178. bool BYieldingLockSteamID( const CSteamID &steamID, const char *pszFilename, int nLineNum );
  179. bool BYieldingLockSteamIDPair( const CSteamID &steamIDA, const CSteamID &steamIDB, const char *pszFilename, int nLineNum );
  180. bool BLockSteamIDImmediate( const CSteamID &steamID );
  181. void UnlockSteamID( const CSteamID &steamID );
  182. bool IsSteamIDLocked( const CSteamID &steamID );
  183. bool IsSteamIDLockedByJob( const CSteamID &steamID, const CJob *pJob ) const;
  184. bool IsSteamIDLockedByCurJob( const CSteamID &steamID ) const;
  185. bool IsSteamIDUnlockedOrLockedByCurJob( const CSteamID &steamID );
  186. CJob *PJobHoldingLock( const CSteamID &steamID );
  187. const CLock *FindSteamIDLock( const CSteamID &steamID );
  188. void DumpSteamIDLocks( bool bFull, int nMax = 10 );
  189. void DumpJobs( const char *pszJobName, int nMax, int nPrintLocksMax ) const;
  190. void DumpJob( JobID_t jobID, int nPrintLocksMax ) const;
  191. virtual void Dump() const;
  192. void VerifySOCacheLRU();
  193. void SetProfilingEnabled( bool bEnabled );
  194. void SetDumpVprofImbalances( bool bEnabled );
  195. bool GetVprofImbalances();
  196. bool YieldingWritebackDirtyCaches( uint32 unSecondToDelayWrite );
  197. void AddCacheToWritebackQueue( CGCSharedObjectCache *pSOCache );
  198. bool BYieldingRetrieveCacheVersion( CGCSharedObjectCache *pSOCache );
  199. void AddCacheToVersionChangedList( CGCSharedObjectCache *pSOCache );
  200. const char *GetCDNURL() const;
  201. struct GCMemcachedBuffer_t
  202. {
  203. const void *m_pubData;
  204. int m_cubData;
  205. };
  206. struct GCMemcachedGetResult_t
  207. {
  208. bool m_bKeyFound; // true if the key was found
  209. CUtlAllocation m_bufValue; // the value of the key
  210. };
  211. // Memcached access
  212. bool BMemcachedSet( const char *pKey, const ::google::protobuf::Message &protoBufObj );
  213. bool BMemcachedDelete( const char *pKey );
  214. bool BYieldingMemcachedGet( const char *pKey, ::google::protobuf::Message &protoBufObj );
  215. bool BMemcachedSet( const CUtlString &strKey, const CUtlBuffer &buf );
  216. bool BMemcachedSet( const CUtlVector<CUtlString> &vecKeys, const CUtlVector<GCMemcachedBuffer_t> &vecValues );
  217. bool BMemcachedDelete( const CUtlString &strKey );
  218. bool BMemcachedDelete( const CUtlVector<CUtlString> &vecKeys );
  219. bool BYieldingMemcachedGet( const CUtlString &strKey, GCMemcachedGetResult_t &result );
  220. bool BYieldingMemcachedGet( const CUtlVector<CUtlString> &vecKeys, CUtlVector<GCMemcachedGetResult_t> &vecResults );
  221. // IP location
  222. bool BYieldingGetIPLocations( CUtlVector<uint32> &vecIPs, CUtlVector<CIPLocationInfo> &infos );
  223. /// Check if any of the sessions don't have geolocation info, then fetch
  224. /// the info we can. The supplied SteamID's may refer to individuals or
  225. /// game servers
  226. bool BYieldingUpdateGeoLocation( CUtlVector<CSteamID> const &requestedVecSteamIds );
  227. // Stats
  228. virtual void SystemStats_Update( CGCMsgGetSystemStatsResponse &msgStats );
  229. //called to determine the amount of uptime this GC has had
  230. uint32 GetGCUpTime() const;
  231. RTime32 GetGCInitTime() const { return m_nInitTime; }
  232. protected:
  233. virtual bool BYieldingLoadSOCache( CGCSharedObjectCache *pSOCache );
  234. void RemoveSOCache( const CSteamID & steamID );
  235. void AddCacheToLRU( CGCSharedObjectCache * pSOCache );
  236. void RemoveCacheFromLRU( CGCSharedObjectCache * pSOCache );
  237. void SetStartupComplete() { m_bStartupComplete = true; }
  238. private:
  239. static void AssertCallbackFunc( const char *pchFile, int nLine, const char *pchMessage );
  240. void UpdateSOCacheVersions();
  241. //
  242. // Create and initialize player / gameserver sessions. This should be called only from two places:
  243. // - YieldingExecuteNextStartPlaying()
  244. // - YieldingRequestSession(), which can be called ANY TIME we ask for a locked session but don't have one
  245. //
  246. void YieldingStartPlaying( const CSteamID & steamID, const CSteamID & gsSteamID, uint32 unServerAddr, uint16 usServerPort, CUtlBuffer *pVarData );
  247. void YieldingStartGameserver( const CSteamID & steamID, uint32 unServerAddr, uint16 usServerPort, const uint8 *pubVarData, uint32 cubVarData );
  248. void YieldingExecuteStartPlayingQueueEntryByIndex( int idxStartPlayingQueue );
  249. // Base behaviors of the IGameCoordinator interface. These are not overridable.
  250. // They each call the On* version below, which is overridable by subclasses
  251. virtual bool BInit( AppId_t unAppID, const char *pchAppPath, IGameCoordinatorHost *pHost );
  252. virtual bool BMainLoopOncePerFrame( uint64 ulLimitMicroseconds );
  253. virtual bool BMainLoopUntilFrameCompletion( uint64 ulLimitMicroseconds );
  254. virtual void Shutdown();
  255. virtual void Uninit();
  256. virtual void MessageFromClient( const CSteamID & senderID, uint32 unMsgType, void *pubData, uint32 cubData );
  257. virtual void Validate( CValidator &validator, const char *pchName );
  258. virtual void SQLResults( GID_t gidContextID );
  259. static void SetUserSessionDetails( CGCUserSession *pUserSession, KeyValues *pkvDetails );
  260. // Remembers the console spew func we overrode so we can restore it at uninit
  261. SpewOutputFunc_t m_OutputFuncPrev;
  262. // profiling
  263. bool m_bStartProfiling;
  264. bool m_bStopProfiling;
  265. bool m_bDumpVprofImbalances;
  266. //the time that the GC started so that we can compute uptime
  267. RTime32 m_nInitTime;
  268. RTime32 m_nStartupCompleteTime;
  269. // local job handling
  270. CJobMgr m_JobMgr;
  271. CGCWGJobMgr m_wgJobMgr;
  272. // session tracking
  273. CTHash<CGCUserSession *, uint64> m_hashUserSessions;
  274. CTHash<CGCGSSession *, uint64> m_hashGSSessions;
  275. CUtlHashMapLarge< uint64, CGCGSSession* > m_mapGSSessionsByNetAddress;
  276. // Shared object caches
  277. CUtlHashMapLarge<CSteamID, CGCSharedObjectCache *> m_mapSOCache;
  278. CUtlVector< CGCSharedObjectCache * >m_vecCacheWritebacks;
  279. CUtlLinkedList< CSteamID, uint32> m_listCachesToUnload;
  280. CUtlRBTree<CSteamID, int > m_rbtreeSOCachesBeingLoaded;
  281. CUtlRBTree<CSteamID, int > m_rbtreeSOCachesWithDirtyVersions;
  282. CUtlRBTree< AccountID_t, int32, CDefLess< AccountID_t > > m_rbFlushInventoryCacheAccounts;
  283. JobID_t m_jobidFlushInventoryCacheAccounts;
  284. uint32 m_numFlushInventoryCacheAccountsLastScheduled;
  285. // steamID locks
  286. CTHash<CLock, CSteamID> m_hashSteamIDLocks;
  287. // Account Details
  288. CAccountDetailsManager m_AccountDetailsManager;
  289. // State
  290. AppId_t m_unAppID;
  291. CUtlString m_sPath;
  292. IGameCoordinatorHost *m_pHost;
  293. bool m_bStartupComplete;
  294. bool m_bIsShuttingDown;
  295. struct StartPlayingWork_t
  296. {
  297. CSteamID m_steamID;
  298. CSteamID m_gsSteamID;
  299. uint32 m_unServerAddr;
  300. uint16 m_usServerPort;
  301. CUtlBuffer *m_pVarData;
  302. };
  303. CUtlLinkedList< StartPlayingWork_t, int > m_llStartPlaying;
  304. CUtlMap< CSteamID, int, int > m_mapStartPlayingQueueIndexBySteamID;
  305. /// Number of active jobs
  306. int m_nStartPlayingJobCount;
  307. // URL to use for our app's CDN'd images
  308. mutable CUtlString m_sCDNURL;
  309. /// Number of active jobs currently inside YieldingRequestSession
  310. int m_nRequestSessionJobsActive;
  311. /// Number of frames to wait before checking to
  312. /// see if we're done with logon surge. This will
  313. /// be nonzero while in logon surge, and zero
  314. /// if we're not in logon surge.
  315. int m_nLogonSurgeFramesRemaining;
  316. //rate limiting system
  317. CSteamIDRateLimit m_MsgRateLimit;
  318. //a map of the HTTP error messages so we can batch them up as they occur as they tend to be very spammy
  319. void ReportHTTPError( const char* pszError, CGCEmitGroup::EMsgLevel eLevel );
  320. void DumpHTTPErrors();
  321. struct SHTTPError
  322. {
  323. CUtlString m_sStr;
  324. uint32 m_nCount;
  325. CGCEmitGroup::EMsgLevel m_eSeverity;
  326. };
  327. CUtlHashMapLarge< const char*, SHTTPError*, CaseSensitiveStrEquals, MurmurHash3ConstCharPtr > m_HTTPErrors;
  328. CScheduledFunction< CGCBase > m_DumpHTTPErrorsSchedule;
  329. };
  330. extern CGCBase *GGCBase();
  331. // ----------------------------------------------------------------------------
  332. // BEGIN BLOCK OF PRE-TF-GC-SPLIT SCAFFOLDING
  333. // ----------------------------------------------------------------------------
  334. // Alright, here's why this mess is here: we (TF) are doing some work that depends
  335. // on work that Dota has done, things like the SQL message queue, etc. We want to
  336. // update our code so that it does the right thing and uses their functionaity so
  337. // that they can then integrate our changes.
  338. //
  339. // TF is way out of date, though, and so rather than make that work, some of which
  340. // has significant time pressure, dependent on a massive weeks-long integrate that
  341. // we aren't sure is the right thing to do anyway, we're going to bring over their
  342. // code as-is and then stub in implementations.
  343. //
  344. // This horrific template mess basically says "are we compiling in an environment
  345. // with a spit GC?" (specifically, does CGCBase::GetGCType() exist?). If so, call
  346. // into real implementations for things like GetGCGType(), etc. If not, feed back
  347. // sane default values (ie., we're a master GC and there's only one of us). Doing
  348. // this means that Dota can integrate our code and we can integrate Dota code and
  349. // no callsites have to change. If/when TF *does* split our GC, things will silently
  350. // update their implementation and then we can delete the scaffolding whenever.
  351. template < class tBaseGCType >
  352. struct IsSplitGC
  353. {
  354. typedef char t_Yes[1];
  355. typedef char t_No[2];
  356. template < typename T, T >
  357. struct InternalTypeCheck; \
  358. template < typename T >
  359. static t_Yes& Test( InternalTypeCheck< uint32(), &T::GetGCType> * );
  360. template < typename T >
  361. static t_No& Test( ... );
  362. enum { kValue = sizeof( Test<tBaseGCType>( NULL ) ) == sizeof( t_Yes ) };
  363. };
  364. template < bool tTest, typename T > struct EnableIf { typedef T Type; };
  365. template < typename T > struct EnableIf< false, T > { };
  366. #define SplitVsSingleGCImpl( returntype_, functionandparams_, splitgcimpl_, singlegcimpl_ ) \
  367. template < class tBaseGCType = CGCBase > typename EnableIf<IsSplitGC<tBaseGCType>::kValue, returntype_>::Type functionandparams_ { return splitgcimpl_; } \
  368. template < class tBaseGCType = CGCBase > typename EnableIf<!IsSplitGC<tBaseGCType>::kValue, returntype_>::Type functionandparams_ { return singlegcimpl_; }
  369. SplitVsSingleGCImpl( uint32, GGetGCType(), GGCBase()->GetGCType(), 0 );
  370. SplitVsSingleGCImpl( uint32, GGetGCCountForType( uint32 unGCType ), GDirectory()->GetGCCountForType( unGCType ), 1 );
  371. SplitVsSingleGCImpl( uint32, GGetGCInstance(), GGCBase()->GetDirInfo()->GetInstance(), 0 );
  372. #undef SplitVsSingleGCImpl
  373. // ----------------------------------------------------------------------------
  374. // END BLOCK OF TEMPORARY PRE-INTEGRATE SCAFFOLDING
  375. // ----------------------------------------------------------------------------
  376. EResult YieldingSendWebAPIRequest( CSteamAPIRequest &request, KeyValues *pKVResponse, CUtlString &errMsg, bool b200MeansSuccess );
  377. /// Scope object that remembers if a Steam ID is locked, and automatically unlocks it upon destruction
  378. class CScopedSteamIDLock
  379. {
  380. public:
  381. /// Create object without assigning SteamID
  382. CScopedSteamIDLock() : m_steamID(), m_bLockedSuccesfully(false), m_iSavedLockCount(-1) {}
  383. /// Construct object to lock a particular steam ID
  384. CScopedSteamIDLock( const CSteamID& steamID ) : m_steamID( steamID ), m_bLockedSuccesfully(false), m_iSavedLockCount(-1) {}
  385. /// Destructor performs an unlock, if we are holing a lock. That's pretty much the whole purpose of this class.
  386. ~CScopedSteamIDLock() { Unlock(); }
  387. /// Return true if we're locked
  388. bool IsLocked() const { return m_bLockedSuccesfully; }
  389. /// Lock the currently assigned Steam ID. (Set by constructor)
  390. bool BYieldingPerformLock( const char *pszFilename, int nLineNumber )
  391. {
  392. Assert( !m_bLockedSuccesfully );
  393. Unlock();
  394. Assert( m_steamID.IsValid() );
  395. m_bLockedSuccesfully = GGCBase()->BYieldingLockSteamID( m_steamID, pszFilename, nLineNumber );
  396. if ( m_bLockedSuccesfully )
  397. {
  398. const CLock *pLock = GGCBase()->FindSteamIDLock( m_steamID );
  399. if ( pLock )
  400. {
  401. m_iSavedLockCount = pLock->GetReferenceCount();
  402. }
  403. else
  404. {
  405. Assert( pLock );
  406. }
  407. }
  408. return m_bLockedSuccesfully;
  409. }
  410. /// Lock the specified Steam ID.
  411. bool BYieldingPerformLock( const CSteamID &steamID, const char *pszFilename, int nLineNumber )
  412. {
  413. // Should not already be locked, but unlock just in case
  414. Assert( !m_bLockedSuccesfully );
  415. Unlock();
  416. // Attempt lock
  417. m_steamID = steamID;
  418. m_bLockedSuccesfully = GGCBase()->BYieldingLockSteamID( m_steamID, pszFilename, nLineNumber );
  419. if ( m_bLockedSuccesfully )
  420. {
  421. const CLock *pLock = GGCBase()->FindSteamIDLock( m_steamID );
  422. if ( pLock )
  423. {
  424. m_iSavedLockCount = pLock->GetReferenceCount();
  425. }
  426. else
  427. {
  428. Assert( pLock );
  429. }
  430. }
  431. // Report status
  432. return m_bLockedSuccesfully;
  433. }
  434. /// Mark us as already being locked. This is used when you already have a lock, and just want to use
  435. /// the scoped class to do the unlock when you're done
  436. void MarkLocked( const CSteamID &steamID )
  437. {
  438. // Should not already be locked, but unlock just in case
  439. Assert( !m_bLockedSuccesfully );
  440. Unlock();
  441. // Remember state
  442. m_steamID = steamID;
  443. m_bLockedSuccesfully = true;
  444. const CLock *pLock = GGCBase()->FindSteamIDLock( m_steamID );
  445. if ( pLock )
  446. {
  447. m_iSavedLockCount = pLock->GetReferenceCount();
  448. }
  449. else
  450. {
  451. Assert( pLock );
  452. }
  453. }
  454. /// Stop tracking that we're locking this object. This does *not* unlock anything -- Unlock does
  455. /// that. This is for "we hit the end of our scope but now want to do something else with the
  456. /// lock (ie., pass back to a caller)". This is like un-smarting a smart pointer.
  457. void Release()
  458. {
  459. if ( m_bLockedSuccesfully )
  460. {
  461. VerifyPreUnlock();
  462. m_bLockedSuccesfully = false;
  463. m_iSavedLockCount = -1;
  464. }
  465. }
  466. /// Unlock the lock, if we are holding it. Usually this is not called directly,
  467. /// we let the destructor do it.
  468. void Unlock()
  469. {
  470. if ( m_bLockedSuccesfully )
  471. {
  472. VerifyPreUnlock();
  473. GGCBase()->UnlockSteamID( m_steamID );
  474. m_bLockedSuccesfully = false;
  475. m_iSavedLockCount = -1;
  476. }
  477. }
  478. private:
  479. void VerifyPreUnlock() const
  480. {
  481. Assert( m_bLockedSuccesfully );
  482. const CLock *pLock = GGCBase()->FindSteamIDLock( m_steamID );
  483. if ( pLock )
  484. {
  485. AssertMsg1( m_iSavedLockCount == pLock->GetReferenceCount(), "Lock imbalance on %s detected by scope lock", pLock->GetName() );
  486. }
  487. else
  488. {
  489. Assert( pLock );
  490. }
  491. }
  492. private:
  493. CSteamID m_steamID;
  494. bool m_bLockedSuccesfully;
  495. int m_iSavedLockCount;
  496. };
  497. /// Scope object that remembers if a specific lock is taken, and releases it
  498. class CScopedGenericLock
  499. {
  500. public:
  501. /// Create object for specific lock SteamID
  502. CScopedGenericLock( CLock &lock ) : m_pLock( &lock ), m_bLockedSuccesfully(false), m_iSavedLockCount(-1) {}
  503. /// Destructor performs an unlock, if we are holing a lock. That's pretty much the whole purpose of this class.
  504. ~CScopedGenericLock() { Unlock(); }
  505. /// Return true if we're locked
  506. bool IsLocked() const { return m_bLockedSuccesfully; }
  507. /// Lock the current lock. (Set by constructor)
  508. bool BYieldingPerformLock( const char *pszFilename, int nLineNumber )
  509. {
  510. Assert( !m_bLockedSuccesfully );
  511. Unlock();
  512. Assert( m_pLock );
  513. m_bLockedSuccesfully = GJobCur()._BYieldingAcquireLock( m_pLock, pszFilename, nLineNumber );
  514. if ( m_bLockedSuccesfully )
  515. {
  516. m_iSavedLockCount = m_pLock->GetReferenceCount();
  517. }
  518. return m_bLockedSuccesfully;
  519. }
  520. /// Stop tracking that we're locking this object. This does *not* unlock anything -- Unlock does
  521. /// that. This is for "we hit the end of our scope but now want to do something else with the
  522. /// lock (ie., pass back to a caller)". This is like un-smarting a smart pointer.
  523. void Release()
  524. {
  525. if ( m_bLockedSuccesfully )
  526. {
  527. VerifyPreUnlock();
  528. m_bLockedSuccesfully = false;
  529. m_iSavedLockCount = -1;
  530. }
  531. }
  532. /// Unlock the lock, if we are holding it. Usually this is not called directly,
  533. /// we let the destructor do it.
  534. void Unlock()
  535. {
  536. if ( m_bLockedSuccesfully )
  537. {
  538. VerifyPreUnlock();
  539. if ( m_pLock->GetJobLocking() != &GJobCur() )
  540. {
  541. AssertMsg( false, "CScopedGenericLock::Unlock called when job %s doesn't own the lock", GJobCur().GetName() );
  542. return;
  543. }
  544. GJobCur().ReleaseLock( m_pLock );
  545. m_bLockedSuccesfully = false;
  546. m_iSavedLockCount = -1;
  547. }
  548. }
  549. private:
  550. void VerifyPreUnlock() const
  551. {
  552. Assert( m_bLockedSuccesfully );
  553. AssertMsg1( m_iSavedLockCount == m_pLock->GetReferenceCount(), "Lock imbalance on %s detected by scope lock", m_pLock->GetName() );
  554. }
  555. private:
  556. CLock *m_pLock;
  557. bool m_bLockedSuccesfully;
  558. int m_iSavedLockCount;
  559. };
  560. class CTrustedHelper_AssertOnNonPublicUniverseFailures
  561. {
  562. public:
  563. void operator()( const bool bExpResult, const char *pszExp ) const
  564. {
  565. if ( GetUniverse() != k_EUniversePublic )
  566. {
  567. AssertMsg1( bExpResult, "%s", pszExp );
  568. }
  569. }
  570. };
  571. class CVerifyIfTrustedHelper
  572. {
  573. public:
  574. template < typename tTrustedCriteriaImpl >
  575. CVerifyIfTrustedHelper( const tTrustedCriteriaImpl& CriteriaImpl, const bool bExpResult, const char *pszExp )
  576. {
  577. CommonConstructor( CriteriaImpl, bExpResult, pszExp );
  578. }
  579. // Helper constructor so we can do VerifyIfTrusted( pSomePointer ) without getting a type-
  580. // conversion-to-bool warning.
  581. template < typename tTrustedCriteriaImpl >
  582. CVerifyIfTrustedHelper( const tTrustedCriteriaImpl& CriteriaImpl, const void *pointer, const char *pszExp )
  583. {
  584. CommonConstructor( CriteriaImpl, pointer != NULL, pszExp );
  585. }
  586. bool GetResult() const { return m_bExpResult; }
  587. private:
  588. template < typename tTrustedCriteriaImpl >
  589. void CommonConstructor( const tTrustedCriteriaImpl& CriteriaImpl, const bool bExpResult, const char *pszExp )
  590. {
  591. m_bExpResult = bExpResult;
  592. CriteriaImpl( bExpResult, pszExp );
  593. }
  594. bool m_bExpResult;
  595. };
  596. // Examples of things that we don't want to assert on, even during testing:
  597. // - we got a message from someone who didn't have a session. This could be a Steam
  598. // problem, or we could be backlogged processing messages, or we could have an offline
  599. // trade get processed in the background.
  600. // - someone sent up a message involving an item that they don't own. (Same reasons as
  601. // a missing session.)
  602. //
  603. // Examples of things that we do want to assert on internally, but not when running public:
  604. // - our messages match contents fulfill criteria that the client specifies. ie., if we're
  605. // passing up a "victim" and a "killer" Steam ID, they won't be identical.
  606. // - our schema data is set up correctly. ie., necessary fields ("kill eater localization
  607. // string") that we expect to fail a schema parse init for if they're absent are present.
  608. // - our messages are coming from places that make sense. ie., a game server isn't sending
  609. // messages we expect to get from a client, or we aren't getting a message from a client
  610. // that we expect only to come from elsewhere in GC code.
  611. #define VerifyIfTrusted( exp_ ) \
  612. CVerifyIfTrustedHelper( CTrustedHelper_AssertOnNonPublicUniverseFailures(), (exp_), #exp_ ).GetResult()
  613. // !KLUDGE! Shim to make it easier to merge over stuff from DOTA
  614. class CGCInterface
  615. {
  616. public:
  617. CSteamID ConstructSteamIDForClient( AccountID_t unAccountID ) const;
  618. };
  619. extern CGCInterface *GGCInterface();
  620. } // namespace GCSDK
  621. struct CRatelimitedSpewController
  622. {
  623. public:
  624. CRatelimitedSpewController() : m_rtCooldownStart( 0 ), m_rtLastSpew( 0 ), m_numSpewed( 0 ), m_numSkipped( 0 ) {}
  625. ~CRatelimitedSpewController() {}
  626. enum ERate_t
  627. {
  628. k_ERate_Skip = -1, // skip printing anything, rate limit too high
  629. k_ERate_Print = 0, // print the full message
  630. // default is to print how many messages total as well
  631. };
  632. int RegisterSpew();
  633. private:
  634. RTime32 m_rtCooldownStart;
  635. RTime32 m_rtLastSpew;
  636. int m_numSpewed;
  637. int m_numSkipped;
  638. };
  639. #define EmitErrorRatelimited( SPEW_GROUP_ID, fmtstring, ... ) \
  640. static CRatelimitedSpewController s_spewcontroller; \
  641. int nspewcontroller = s_spewcontroller.RegisterSpew(); \
  642. switch ( nspewcontroller ) \
  643. { \
  644. case CRatelimitedSpewController::k_ERate_Skip: break; \
  645. default: \
  646. EmitError( SPEW_GROUP_ID, nspewcontroller \
  647. ? fmtstring "(... and %d more skipped)\n" \
  648. : fmtstring \
  649. , __VA_ARGS__ \
  650. , nspewcontroller \
  651. ); \
  652. break; \
  653. } \
  654. #define EmitInfoRatelimited( SPEW_GROUP_ID, ConsoleLevel, LogLevel, fmtstring, ... ) \
  655. static CRatelimitedSpewController s_spewcontroller; \
  656. int nspewcontroller = s_spewcontroller.RegisterSpew(); \
  657. switch ( nspewcontroller ) \
  658. { \
  659. case CRatelimitedSpewController::k_ERate_Skip: break; \
  660. default: \
  661. EmitInfo( SPEW_GROUP_ID, ConsoleLevel, LogLevel, nspewcontroller \
  662. ? fmtstring "(... and %d more skipped)\n" \
  663. : fmtstring \
  664. , __VA_ARGS__ \
  665. , nspewcontroller \
  666. ); \
  667. break; \
  668. } \
  669. #define EG_WARNING_RATELIMITED( SPEW_GROUP_ID, fmtstring, ... ) \
  670. static CRatelimitedSpewController s_spewcontroller; \
  671. int nspewcontroller = s_spewcontroller.RegisterSpew(); \
  672. switch ( nspewcontroller ) \
  673. { \
  674. case CRatelimitedSpewController::k_ERate_Skip: break; \
  675. default: \
  676. EG_WARNING( SPEW_GROUP_ID, nspewcontroller \
  677. ? fmtstring "(... and %d more skipped)\n" \
  678. : fmtstring \
  679. , __VA_ARGS__ \
  680. , nspewcontroller \
  681. ); \
  682. break; \
  683. } \
  684. #endif // GCBASE_H