diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /public/gcsdk/gcbase.h | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'public/gcsdk/gcbase.h')
| -rw-r--r-- | public/gcsdk/gcbase.h | 834 |
1 files changed, 834 insertions, 0 deletions
diff --git a/public/gcsdk/gcbase.h b/public/gcsdk/gcbase.h new file mode 100644 index 0000000..fbefbc0 --- /dev/null +++ b/public/gcsdk/gcbase.h @@ -0,0 +1,834 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef GCBASE_H +#define GCBASE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "gamecoordinator/igamecoordinator.h" +#include "gamecoordinator/igamecoordinatorhost.h" +#include "tier1/utlallocation.h" +#include "gcmsg.h" +#include "jobmgr.h" +#include "tier1/thash.h" +#include "tier1/UtlSortVector.h" +#include "http.h" +#include "language.h" +#include "accountdetails.h" +#include "gcsession.h" + +class CGCMsgGetSystemStatsResponse; + +extern EUniverse GetUniverse(); + +const int16 k_nLockTypeLobby = 3; +const int16 k_nLockTypeParty = 2; +const int16 k_nLockTypeIndividual = 1; +const int16 k_nLockTypeGameServer = 0; +const int16 k_nLockTypeGroupIDGeneration = -1; + // For one-off locks of specific resources. The specific game controls the subtype ordering. +const int16 k_nLockTypeGameMisc = -10; + +namespace GCSDK +{ +class CGCSession; +class CGCUserSession; +class CGCGSSession; +class CGCSharedObjectCache; +class CSharedObject; +class CAccountDetails; +class CScopedSteamIDLock; + +struct PackageLicense_t +{ + uint32 m_unPackageID; + RTime32 m_rtimeCreated; +}; + +#define SERVER_KEY_HASH 0x5a4f4944u + +class CGCBase : public IGameCoordinator +{ +public: + CGCBase( ); + virtual ~CGCBase(); + + // Hooks to extend the base behaviors of the IGameCoordinator interface + virtual bool OnInit() { return true; } + virtual bool OnMainLoopOncePerFrame( CLimitTimer &limitTimer ) { return false; } + virtual bool OnMainLoopUntilFrameCompletion( CLimitTimer &limitTimer ) { return false; } + virtual void OnUninit() {} + // returns true if this function handled the message + virtual bool OnMessageFromClient( const CSteamID & senderID, uint32 unMsgType, void *pubData, uint32 cubData ) { return false; } + virtual void OnValidate( CValidator &validator, const char *pchName ) {} + virtual const char *LocalizeToken( const char *pchToken, ELanguage eLanguage, bool bReturnTokenIfNotFound = true ) { return bReturnTokenIfNotFound ? pchToken : NULL; } + + virtual void YldOnAccountPhoneVerificationChange( CSteamID steamID ) {} + virtual void YldOnAccountTwoFactorChange( CSteamID steamID ) {} + + /// The main loop calls Run() on game servers and user sessions until a certain amount of time + /// is expired. Thus the user list is iterated, but the iteration is amortized over multiple frames. + /// this function is called when a sweep of the user sessions ends and a new one is about to begin. + virtual void FinishedMainLoopUserSweep(); + + // Life cycle management functions + + // Called to do any yielding initialization work before reporting as fully operational + virtual bool BYieldingFinishStartup() = 0; + + // Called to do any yielding work immediately after becoming full operational + virtual bool BYieldingPostStartup() = 0; + + // Call to report that we're fully operational + void SetStartupComplete( bool bSuccess ); + + // Called to do any yielding work before reporting as ready to shutdown + virtual void YieldingGracefulShutdown() = 0; + + + virtual CGCUserSession *CreateUserSession( const CSteamID & steamID, CGCSharedObjectCache *pSOCache ) const; + virtual CGCGSSession *CreateGSSession( const CSteamID & steamID, CGCSharedObjectCache *pSOCache, uint32 unServerAddr, uint16 usServerPort ) const; + virtual void YieldingSessionStartPlaying( CGCUserSession *pSession ) {} + virtual void YieldingSessionStopPlaying( CGCUserSession *pSession ) {} + virtual void YieldingSessionStartServer( CGCGSSession *pSession ) {} + virtual void YieldingSessionStopServer( CGCGSSession *pSession ) {} + virtual void YieldingSOCacheLoaded( CGCSharedObjectCache *pSOCache ); + virtual void UpdateGSSessionAddress( CGCGSSession *pSession, uint32 unServerAddr, uint16 usServerPort ); + + virtual void YieldingPreTestSetup() {} + + // cache management + CGCSharedObjectCache *YieldingGetLockedSOCache( const CSteamID &steamID, const char *pszFilename, int nLineNum ); + CGCSharedObjectCache *YieldingFindOrLoadSOCache( const CSteamID &steamID ); + CGCSharedObjectCache *FindSOCache( const CSteamID & steamID ); // non-yielding, but may return NULL if the cache exists but is not loaded + bool UnloadUnusedCaches( uint32 unMaxCacheCount, CLimitTimer *pLimitTimer = NULL ); + void YieldingReloadCache( CGCSharedObjectCache *pSOCache ); + virtual CGCSharedObjectCache *CreateSOCache( const CSteamID &steamID ); + void FlushInventoryCache( AccountID_t unAccountID ); + + CGCUserSession *YieldingGetLockedUserSession( const CSteamID & steamID, const char *pszFilename, int nLineNum ); + CGCUserSession *FindUserSession( const CSteamID & steamID ) const; + bool BUserSessionPending( const CSteamID & steamID ) const; + + CGCGSSession *YieldingGetLockedGSSession( const CSteamID & steamID, const char *pszFilename, int nLineNum ); + CGCGSSession *YieldingFindOrCreateGSSession( const CSteamID & steamID, uint32 unServerAddr, uint16 usServerPort, const uint8 *pubVarData, uint32 cubVarData ); + CGCGSSession *FindGSSession( const CSteamID & steamID ) const; + + /// Lookup the user or GS session, depending on whether the supplied + /// Steam ID is an individual or gameserver ID + CGCSession *FindUserOrGSSession( const CSteamID & steamID ) const; + + CGCSession *YieldingRequestSession( const CSteamID & steamID ); + bool BYieldingIsOnline( const CSteamID & steamID ); + bool BSendWebApiRegistration(); + + // Will return true, indicating the request was sent, a response was received, and the response had a valid + // status code, or false, indicating that there was a transport-layer problem -- the request timed out, or + // a response came back with an invalid status code, etc. All "false" return values are meant to indicate + // temporary errors. + bool BYieldingSendHTTPRequest( const CHTTPRequest *pRequest, CHTTPResponse *pResponse ); + + // Possible return values: + // k_EResultOK -- everything went fine and pkvResponse has been populated. + // k_EResultTimeout -- there was a temporary/transport-layer problem (see "BYieldingSendHTTPRequest"). + // These are temporary errors, or programming errors on our side. + // k_EResultRemoteCallFailed -- we didn't timeout but we got a bad, unparseable, or otherwise invalid response + // back and so we don't have data we can use. + // k_EResultFail -- something has gone catastrophically wrong and no forward progress can be + // expected to be made. May or may not ever be returned. + EResult YieldingSendHTTPRequestKV( const CHTTPRequest *pRequest, KeyValues *pkvResponse ); + + int GetSOCacheCount() const; + bool IsSOCached( const CSharedObject *pObj, uint32 nTypeID ) const; + + int GetUserSessionCount() const; + int GetGSSessionCount() const; + + void SetIsShuttingDown(); + bool GetIsShuttingDown() const { return m_bIsShuttingDown; } + + // Iterate over sessions + // WARNING: Don't yield between GetFirst*/GetNext*. Use caution when using + // these any time you might have tens of thousands of sessions to iterate through. + CGCUserSession **GetFirstUserSession() { return m_hashUserSessions.PvRecordFirst(); } + CGCUserSession **GetNextUserSession( CGCUserSession **pUserSession ) { return m_hashUserSessions.PvRecordNext( pUserSession ); } + CGCGSSession **GetFirstGSSession() { return m_hashGSSessions.PvRecordFirst(); } + CGCGSSession **GetNextGSSession( CGCGSSession **pGSSession ) { return m_hashGSSessions.PvRecordNext( pGSSession ); } + + AppId_t GetAppID() const { return m_unAppID; } + bool BIsStartupComplete() const { return m_bStartupComplete; } + CJobMgr &GetJobMgr() { return m_JobMgr; } + + CSteamID YieldingGuessSteamIDFromInput( const char *pchInput ); + bool BYieldingRecordSupportAction( const CSteamID & actorID, const CSteamID & targetID, const char *pchData, const char *pchNote ); + void PostAlert( EAlertType eAlertType, bool bIsCritical, const char *pchAlertText, const CUtlVector< CUtlString > *pvecExtendedInfo = NULL, bool bAlsoSpew = true ); + const CAccountDetails *YieldingGetAccountDetails( const CSteamID & steamID, bool bForceReload = false ); + const char *YieldingGetPersonaName( const CSteamID & steamID, const char *pchUnknownName = NULL ); + void PreloadPersonaName( const CSteamID & steamID ); + void ClearCachedPersonaName( const CSteamID & steamID ); + bool BYieldingGetAccountLicenses( const CSteamID & steamID, CUtlVector< PackageLicense_t > & vecPackages ); + bool BYieldingLookupAccount( EAccountFindType eFindType, const char *pchInput, CUtlVector< CSteamID > *prSteamIDs ); + bool BYieldingAddFreeLicense( const CSteamID & steamID, uint32 unPackageID, uint32 unIPPublic = 0, const char *pchStoreCountryCode = NULL ); + int YieldingGrantGuestPass( const CSteamID & steamID, uint32 unPackageID, uint32 unPassesToGrant = 1, int32 nDaysToExpiration = -1 ); + + bool BSendGCMsgToClient( const CSteamID & steamIDTarget, const CGCMsgBase& msg ); + bool BSendGCMsgToClient( const CSteamID & steamIDTarget, const CProtoBufMsgBase& msg ); + + //sends a message to the system (GCH or GC.exe) + bool BSendSystemMessage( const CGCMsgBase& msg, uint32 *pcubSent = NULL ); + bool BSendSystemMessage( const CProtoBufMsgBase& msg, uint32 *pcubSent = NULL ); + bool BSendSystemMessage( const ::google::protobuf::Message &msgOut, MsgType_t eSendMsg ); + + //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 + //don't match what is expected. This can only be called from within a job + bool BYldSendMessageAndGetReply( const CSteamID &steamIDTarget, CProtoBufMsgBase &msgOut, CProtoBufMsgBase *pMsgIn, MsgType_t eMsg ); + //bool BYldSendGCMessageAndGetReply( int32 nGCDirIndex, CProtoBufMsgBase &msgOut, CProtoBufMsgBase *pMsgIn, MsgType_t eMsg ); + bool BYldSendSystemMessageAndGetReply( CGCMsgBase &msgOut, CGCMsgBase *pMsgIn, MsgType_t eMsg ); + bool BYldSendSystemMessageAndGetReply( CProtoBufMsgBase &msgOut, CProtoBufMsgBase *pMsgIn, MsgType_t eMsg ); + bool BYldSendSystemMessageAndGetReply( const ::google::protobuf::Message &msgSend, MsgType_t eSendMsg, ::google::protobuf::Message *pMsgResponse, MsgType_t eRespondMsg ); + + bool BReplyToMessage( CGCMsgBase &msgOut, const CGCMsgBase &msgIn ); + bool BReplyToMessage( CProtoBufMsgBase &msgOut, const CProtoBufMsgBase &msgIn ); + + //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 + bool BSendGCMsgToClientWithPreSerializedBody( const CSteamID & steamIDTarget, MsgType_t eMsgType, const CMsgProtoBufHeader& hdr, const byte *pubBody, uint32 cubBody ) const; + bool BSendGCMsgToSystemWithPreSerializedBody( MsgType_t eMsgType, const CMsgProtoBufHeader& hdr, const byte *pubBody, uint32 cubBody ) const; + bool BReplyToMessageWithPreSerializedBody( MsgType_t eMsgType, const CProtoBufMsgBase &msgIn, const byte *pubBody, uint32 cubBody ) const; + + const char *GetPath() const { return m_sPath; } + virtual const char *GetSteamAPIKey(); + + void QueueStartPlaying( const CSteamID & steamID, const CSteamID & gsSteamID, uint32 unServerAddr, uint16 usServerPort, const uint8 *pubVarData, uint32 cubVarData ); + void YieldingExecuteNextStartPlaying(); + bool BIsInLogonSurge() const { return m_nLogonSurgeFramesRemaining > 0; } + + /// Are we too busy to process any "low service level guarantee" WebAPI jobs? + bool BShouldThrottleLowServiceLevelWebAPIJobs() const; + + /// Removes the entry in the start playing queue for the specified + /// Steam ID, if one exists. Returns true if it was found + /// and removed, false if no entry existed + bool BRemoveStartPlayingQueueEntry( const CSteamID & steamID ); + + void YieldingStopPlaying( const CSteamID & steamID ); + void YieldingStopGameserver( const CSteamID & steamID ); + bool BIsSOCacheBeingLoaded( const CSteamID & steamID ) const { return m_rbtreeSOCachesBeingLoaded.Find( steamID ) != m_rbtreeSOCachesBeingLoaded.InvalidIndex(); } + + bool BYieldingLockSteamID( const CSteamID &steamID, const char *pszFilename, int nLineNum ); + bool BYieldingLockSteamIDPair( const CSteamID &steamIDA, const CSteamID &steamIDB, const char *pszFilename, int nLineNum ); + bool BLockSteamIDImmediate( const CSteamID &steamID ); + void UnlockSteamID( const CSteamID &steamID ); + bool IsSteamIDLocked( const CSteamID &steamID ); + bool IsSteamIDLockedByJob( const CSteamID &steamID, const CJob *pJob ) const; + bool IsSteamIDLockedByCurJob( const CSteamID &steamID ) const; + bool IsSteamIDUnlockedOrLockedByCurJob( const CSteamID &steamID ); + CJob *PJobHoldingLock( const CSteamID &steamID ); + + const CLock *FindSteamIDLock( const CSteamID &steamID ); + void DumpSteamIDLocks( bool bFull, int nMax = 10 ); + void DumpJobs( const char *pszJobName, int nMax, int nPrintLocksMax ) const; + void DumpJob( JobID_t jobID, int nPrintLocksMax ) const; + virtual void Dump() const; + void VerifySOCacheLRU(); + + void SetProfilingEnabled( bool bEnabled ); + void SetDumpVprofImbalances( bool bEnabled ); + bool GetVprofImbalances(); + + bool YieldingWritebackDirtyCaches( uint32 unSecondToDelayWrite ); + void AddCacheToWritebackQueue( CGCSharedObjectCache *pSOCache ); + + bool BYieldingRetrieveCacheVersion( CGCSharedObjectCache *pSOCache ); + void AddCacheToVersionChangedList( CGCSharedObjectCache *pSOCache ); + + const char *GetCDNURL() const; + + struct GCMemcachedBuffer_t + { + const void *m_pubData; + int m_cubData; + }; + + struct GCMemcachedGetResult_t + { + bool m_bKeyFound; // true if the key was found + CUtlAllocation m_bufValue; // the value of the key + }; + + // Memcached access + bool BMemcachedSet( const char *pKey, const ::google::protobuf::Message &protoBufObj ); + bool BMemcachedDelete( const char *pKey ); + bool BYieldingMemcachedGet( const char *pKey, ::google::protobuf::Message &protoBufObj ); + bool BMemcachedSet( const CUtlString &strKey, const CUtlBuffer &buf ); + bool BMemcachedSet( const CUtlVector<CUtlString> &vecKeys, const CUtlVector<GCMemcachedBuffer_t> &vecValues ); + bool BMemcachedDelete( const CUtlString &strKey ); + bool BMemcachedDelete( const CUtlVector<CUtlString> &vecKeys ); + bool BYieldingMemcachedGet( const CUtlString &strKey, GCMemcachedGetResult_t &result ); + bool BYieldingMemcachedGet( const CUtlVector<CUtlString> &vecKeys, CUtlVector<GCMemcachedGetResult_t> &vecResults ); + + // IP location + bool BYieldingGetIPLocations( CUtlVector<uint32> &vecIPs, CUtlVector<CIPLocationInfo> &infos ); + + /// Check if any of the sessions don't have geolocation info, then fetch + /// the info we can. The supplied SteamID's may refer to individuals or + /// game servers + bool BYieldingUpdateGeoLocation( CUtlVector<CSteamID> const &requestedVecSteamIds ); + + // Stats + virtual void SystemStats_Update( CGCMsgGetSystemStatsResponse &msgStats ); + + //called to determine the amount of uptime this GC has had + uint32 GetGCUpTime() const; + RTime32 GetGCInitTime() const { return m_nInitTime; } + +protected: + virtual bool BYieldingLoadSOCache( CGCSharedObjectCache *pSOCache ); + void RemoveSOCache( const CSteamID & steamID ); + + void AddCacheToLRU( CGCSharedObjectCache * pSOCache ); + void RemoveCacheFromLRU( CGCSharedObjectCache * pSOCache ); + + void SetStartupComplete() { m_bStartupComplete = true; } +private: + + static void AssertCallbackFunc( const char *pchFile, int nLine, const char *pchMessage ); + void UpdateSOCacheVersions(); + + // + // Create and initialize player / gameserver sessions. This should be called only from two places: + // - YieldingExecuteNextStartPlaying() + // - YieldingRequestSession(), which can be called ANY TIME we ask for a locked session but don't have one + // + void YieldingStartPlaying( const CSteamID & steamID, const CSteamID & gsSteamID, uint32 unServerAddr, uint16 usServerPort, CUtlBuffer *pVarData ); + void YieldingStartGameserver( const CSteamID & steamID, uint32 unServerAddr, uint16 usServerPort, const uint8 *pubVarData, uint32 cubVarData ); + + void YieldingExecuteStartPlayingQueueEntryByIndex( int idxStartPlayingQueue ); + + // Base behaviors of the IGameCoordinator interface. These are not overridable. + // They each call the On* version below, which is overridable by subclasses + virtual bool BInit( AppId_t unAppID, const char *pchAppPath, IGameCoordinatorHost *pHost ); + virtual bool BMainLoopOncePerFrame( uint64 ulLimitMicroseconds ); + virtual bool BMainLoopUntilFrameCompletion( uint64 ulLimitMicroseconds ); + virtual void Shutdown(); + virtual void Uninit(); + virtual void MessageFromClient( const CSteamID & senderID, uint32 unMsgType, void *pubData, uint32 cubData ); + virtual void Validate( CValidator &validator, const char *pchName ); + virtual void SQLResults( GID_t gidContextID ); + + static void SetUserSessionDetails( CGCUserSession *pUserSession, KeyValues *pkvDetails ); + + // Remembers the console spew func we overrode so we can restore it at uninit + SpewOutputFunc_t m_OutputFuncPrev; + + // profiling + bool m_bStartProfiling; + bool m_bStopProfiling; + bool m_bDumpVprofImbalances; + + //the time that the GC started so that we can compute uptime + RTime32 m_nInitTime; + RTime32 m_nStartupCompleteTime; + + // local job handling + CJobMgr m_JobMgr; + CGCWGJobMgr m_wgJobMgr; + + // session tracking + CTHash<CGCUserSession *, uint64> m_hashUserSessions; + CTHash<CGCGSSession *, uint64> m_hashGSSessions; + CUtlHashMapLarge< uint64, CGCGSSession* > m_mapGSSessionsByNetAddress; + + // Shared object caches + CUtlHashMapLarge<CSteamID, CGCSharedObjectCache *> m_mapSOCache; + CUtlVector< CGCSharedObjectCache * >m_vecCacheWritebacks; + CUtlLinkedList< CSteamID, uint32> m_listCachesToUnload; + CUtlRBTree<CSteamID, int > m_rbtreeSOCachesBeingLoaded; + CUtlRBTree<CSteamID, int > m_rbtreeSOCachesWithDirtyVersions; + CUtlRBTree< AccountID_t, int32, CDefLess< AccountID_t > > m_rbFlushInventoryCacheAccounts; + JobID_t m_jobidFlushInventoryCacheAccounts; + uint32 m_numFlushInventoryCacheAccountsLastScheduled; + + // steamID locks + CTHash<CLock, CSteamID> m_hashSteamIDLocks; + + // Account Details + CAccountDetailsManager m_AccountDetailsManager; + + // State + AppId_t m_unAppID; + CUtlString m_sPath; + IGameCoordinatorHost *m_pHost; + bool m_bStartupComplete; + bool m_bIsShuttingDown; + + struct StartPlayingWork_t + { + CSteamID m_steamID; + CSteamID m_gsSteamID; + uint32 m_unServerAddr; + uint16 m_usServerPort; + CUtlBuffer *m_pVarData; + }; + CUtlLinkedList< StartPlayingWork_t, int > m_llStartPlaying; + CUtlMap< CSteamID, int, int > m_mapStartPlayingQueueIndexBySteamID; + + /// Number of active jobs + int m_nStartPlayingJobCount; + + // URL to use for our app's CDN'd images + mutable CUtlString m_sCDNURL; + + /// Number of active jobs currently inside YieldingRequestSession + int m_nRequestSessionJobsActive; + + /// Number of frames to wait before checking to + /// see if we're done with logon surge. This will + /// be nonzero while in logon surge, and zero + /// if we're not in logon surge. + int m_nLogonSurgeFramesRemaining; + + //rate limiting system + CSteamIDRateLimit m_MsgRateLimit; + + //a map of the HTTP error messages so we can batch them up as they occur as they tend to be very spammy + void ReportHTTPError( const char* pszError, CGCEmitGroup::EMsgLevel eLevel ); + void DumpHTTPErrors(); + struct SHTTPError + { + CUtlString m_sStr; + uint32 m_nCount; + CGCEmitGroup::EMsgLevel m_eSeverity; + }; + CUtlHashMapLarge< const char*, SHTTPError*, CaseSensitiveStrEquals, MurmurHash3ConstCharPtr > m_HTTPErrors; + CScheduledFunction< CGCBase > m_DumpHTTPErrorsSchedule; +}; + +extern CGCBase *GGCBase(); + +// ---------------------------------------------------------------------------- +// BEGIN BLOCK OF PRE-TF-GC-SPLIT SCAFFOLDING +// ---------------------------------------------------------------------------- + +// Alright, here's why this mess is here: we (TF) are doing some work that depends +// on work that Dota has done, things like the SQL message queue, etc. We want to +// update our code so that it does the right thing and uses their functionaity so +// that they can then integrate our changes. +// +// TF is way out of date, though, and so rather than make that work, some of which +// has significant time pressure, dependent on a massive weeks-long integrate that +// we aren't sure is the right thing to do anyway, we're going to bring over their +// code as-is and then stub in implementations. +// +// This horrific template mess basically says "are we compiling in an environment +// with a spit GC?" (specifically, does CGCBase::GetGCType() exist?). If so, call +// into real implementations for things like GetGCGType(), etc. If not, feed back +// sane default values (ie., we're a master GC and there's only one of us). Doing +// this means that Dota can integrate our code and we can integrate Dota code and +// no callsites have to change. If/when TF *does* split our GC, things will silently +// update their implementation and then we can delete the scaffolding whenever. +template < class tBaseGCType > +struct IsSplitGC +{ + typedef char t_Yes[1]; + typedef char t_No[2]; + + template < typename T, T > + struct InternalTypeCheck; \ + + template < typename T > + static t_Yes& Test( InternalTypeCheck< uint32(), &T::GetGCType> * ); + + template < typename T > + static t_No& Test( ... ); + + enum { kValue = sizeof( Test<tBaseGCType>( NULL ) ) == sizeof( t_Yes ) }; +}; + +template < bool tTest, typename T > struct EnableIf { typedef T Type; }; +template < typename T > struct EnableIf< false, T > { }; + +#define SplitVsSingleGCImpl( returntype_, functionandparams_, splitgcimpl_, singlegcimpl_ ) \ + template < class tBaseGCType = CGCBase > typename EnableIf<IsSplitGC<tBaseGCType>::kValue, returntype_>::Type functionandparams_ { return splitgcimpl_; } \ + template < class tBaseGCType = CGCBase > typename EnableIf<!IsSplitGC<tBaseGCType>::kValue, returntype_>::Type functionandparams_ { return singlegcimpl_; } + +SplitVsSingleGCImpl( uint32, GGetGCType(), GGCBase()->GetGCType(), 0 ); +SplitVsSingleGCImpl( uint32, GGetGCCountForType( uint32 unGCType ), GDirectory()->GetGCCountForType( unGCType ), 1 ); +SplitVsSingleGCImpl( uint32, GGetGCInstance(), GGCBase()->GetDirInfo()->GetInstance(), 0 ); + +#undef SplitVsSingleGCImpl + +// ---------------------------------------------------------------------------- +// END BLOCK OF TEMPORARY PRE-INTEGRATE SCAFFOLDING +// ---------------------------------------------------------------------------- + +EResult YieldingSendWebAPIRequest( CSteamAPIRequest &request, KeyValues *pKVResponse, CUtlString &errMsg, bool b200MeansSuccess ); + +/// Scope object that remembers if a Steam ID is locked, and automatically unlocks it upon destruction +class CScopedSteamIDLock +{ +public: + + /// Create object without assigning SteamID + CScopedSteamIDLock() : m_steamID(), m_bLockedSuccesfully(false), m_iSavedLockCount(-1) {} + + /// Construct object to lock a particular steam ID + CScopedSteamIDLock( const CSteamID& steamID ) : m_steamID( steamID ), m_bLockedSuccesfully(false), m_iSavedLockCount(-1) {} + + /// Destructor performs an unlock, if we are holing a lock. That's pretty much the whole purpose of this class. + ~CScopedSteamIDLock() { Unlock(); } + + /// Return true if we're locked + bool IsLocked() const { return m_bLockedSuccesfully; } + + /// Lock the currently assigned Steam ID. (Set by constructor) + bool BYieldingPerformLock( const char *pszFilename, int nLineNumber ) + { + Assert( !m_bLockedSuccesfully ); + Unlock(); + + Assert( m_steamID.IsValid() ); + + m_bLockedSuccesfully = GGCBase()->BYieldingLockSteamID( m_steamID, pszFilename, nLineNumber ); + if ( m_bLockedSuccesfully ) + { + const CLock *pLock = GGCBase()->FindSteamIDLock( m_steamID ); + if ( pLock ) + { + m_iSavedLockCount = pLock->GetReferenceCount(); + } + else + { + Assert( pLock ); + } + } + return m_bLockedSuccesfully; + } + + /// Lock the specified Steam ID. + bool BYieldingPerformLock( const CSteamID &steamID, const char *pszFilename, int nLineNumber ) + { + + // Should not already be locked, but unlock just in case + Assert( !m_bLockedSuccesfully ); + Unlock(); + + // Attempt lock + m_steamID = steamID; + m_bLockedSuccesfully = GGCBase()->BYieldingLockSteamID( m_steamID, pszFilename, nLineNumber ); + if ( m_bLockedSuccesfully ) + { + const CLock *pLock = GGCBase()->FindSteamIDLock( m_steamID ); + if ( pLock ) + { + m_iSavedLockCount = pLock->GetReferenceCount(); + } + else + { + Assert( pLock ); + } + } + + // Report status + return m_bLockedSuccesfully; + } + + /// Mark us as already being locked. This is used when you already have a lock, and just want to use + /// the scoped class to do the unlock when you're done + void MarkLocked( const CSteamID &steamID ) + { + + // Should not already be locked, but unlock just in case + Assert( !m_bLockedSuccesfully ); + Unlock(); + + // Remember state + m_steamID = steamID; + m_bLockedSuccesfully = true; + const CLock *pLock = GGCBase()->FindSteamIDLock( m_steamID ); + if ( pLock ) + { + m_iSavedLockCount = pLock->GetReferenceCount(); + } + else + { + Assert( pLock ); + } + } + + /// Stop tracking that we're locking this object. This does *not* unlock anything -- Unlock does + /// that. This is for "we hit the end of our scope but now want to do something else with the + /// lock (ie., pass back to a caller)". This is like un-smarting a smart pointer. + void Release() + { + if ( m_bLockedSuccesfully ) + { + VerifyPreUnlock(); + m_bLockedSuccesfully = false; + m_iSavedLockCount = -1; + } + } + + /// Unlock the lock, if we are holding it. Usually this is not called directly, + /// we let the destructor do it. + void Unlock() + { + if ( m_bLockedSuccesfully ) + { + VerifyPreUnlock(); + GGCBase()->UnlockSteamID( m_steamID ); + m_bLockedSuccesfully = false; + m_iSavedLockCount = -1; + } + } + +private: + void VerifyPreUnlock() const + { + Assert( m_bLockedSuccesfully ); + + const CLock *pLock = GGCBase()->FindSteamIDLock( m_steamID ); + if ( pLock ) + { + AssertMsg1( m_iSavedLockCount == pLock->GetReferenceCount(), "Lock imbalance on %s detected by scope lock", pLock->GetName() ); + } + else + { + Assert( pLock ); + } + } + +private: + CSteamID m_steamID; + bool m_bLockedSuccesfully; + int m_iSavedLockCount; + +}; + +/// Scope object that remembers if a specific lock is taken, and releases it +class CScopedGenericLock +{ +public: + + /// Create object for specific lock SteamID + CScopedGenericLock( CLock &lock ) : m_pLock( &lock ), m_bLockedSuccesfully(false), m_iSavedLockCount(-1) {} + + /// Destructor performs an unlock, if we are holing a lock. That's pretty much the whole purpose of this class. + ~CScopedGenericLock() { Unlock(); } + + /// Return true if we're locked + bool IsLocked() const { return m_bLockedSuccesfully; } + + /// Lock the current lock. (Set by constructor) + bool BYieldingPerformLock( const char *pszFilename, int nLineNumber ) + { + Assert( !m_bLockedSuccesfully ); + Unlock(); + + Assert( m_pLock ); + + m_bLockedSuccesfully = GJobCur()._BYieldingAcquireLock( m_pLock, pszFilename, nLineNumber ); + if ( m_bLockedSuccesfully ) + { + m_iSavedLockCount = m_pLock->GetReferenceCount(); + } + return m_bLockedSuccesfully; + } + + /// Stop tracking that we're locking this object. This does *not* unlock anything -- Unlock does + /// that. This is for "we hit the end of our scope but now want to do something else with the + /// lock (ie., pass back to a caller)". This is like un-smarting a smart pointer. + void Release() + { + if ( m_bLockedSuccesfully ) + { + VerifyPreUnlock(); + m_bLockedSuccesfully = false; + m_iSavedLockCount = -1; + } + } + + /// Unlock the lock, if we are holding it. Usually this is not called directly, + /// we let the destructor do it. + void Unlock() + { + if ( m_bLockedSuccesfully ) + { + VerifyPreUnlock(); + if ( m_pLock->GetJobLocking() != &GJobCur() ) + { + AssertMsg( false, "CScopedGenericLock::Unlock called when job %s doesn't own the lock", GJobCur().GetName() ); + return; + } + + GJobCur().ReleaseLock( m_pLock ); + m_bLockedSuccesfully = false; + m_iSavedLockCount = -1; + } + } + +private: + void VerifyPreUnlock() const + { + Assert( m_bLockedSuccesfully ); + + AssertMsg1( m_iSavedLockCount == m_pLock->GetReferenceCount(), "Lock imbalance on %s detected by scope lock", m_pLock->GetName() ); + } + +private: + CLock *m_pLock; + bool m_bLockedSuccesfully; + int m_iSavedLockCount; + +}; + +class CTrustedHelper_AssertOnNonPublicUniverseFailures +{ +public: + void operator()( const bool bExpResult, const char *pszExp ) const + { + if ( GetUniverse() != k_EUniversePublic ) + { + AssertMsg1( bExpResult, "%s", pszExp ); + } + } +}; + +class CVerifyIfTrustedHelper +{ +public: + template < typename tTrustedCriteriaImpl > + CVerifyIfTrustedHelper( const tTrustedCriteriaImpl& CriteriaImpl, const bool bExpResult, const char *pszExp ) + { + CommonConstructor( CriteriaImpl, bExpResult, pszExp ); + } + + // Helper constructor so we can do VerifyIfTrusted( pSomePointer ) without getting a type- + // conversion-to-bool warning. + template < typename tTrustedCriteriaImpl > + CVerifyIfTrustedHelper( const tTrustedCriteriaImpl& CriteriaImpl, const void *pointer, const char *pszExp ) + { + CommonConstructor( CriteriaImpl, pointer != NULL, pszExp ); + } + + bool GetResult() const { return m_bExpResult; } + +private: + template < typename tTrustedCriteriaImpl > + void CommonConstructor( const tTrustedCriteriaImpl& CriteriaImpl, const bool bExpResult, const char *pszExp ) + { + m_bExpResult = bExpResult; + + CriteriaImpl( bExpResult, pszExp ); + } + + bool m_bExpResult; +}; + +// Examples of things that we don't want to assert on, even during testing: +// - we got a message from someone who didn't have a session. This could be a Steam +// problem, or we could be backlogged processing messages, or we could have an offline +// trade get processed in the background. +// - someone sent up a message involving an item that they don't own. (Same reasons as +// a missing session.) +// +// Examples of things that we do want to assert on internally, but not when running public: +// - our messages match contents fulfill criteria that the client specifies. ie., if we're +// passing up a "victim" and a "killer" Steam ID, they won't be identical. +// - our schema data is set up correctly. ie., necessary fields ("kill eater localization +// string") that we expect to fail a schema parse init for if they're absent are present. +// - our messages are coming from places that make sense. ie., a game server isn't sending +// messages we expect to get from a client, or we aren't getting a message from a client +// that we expect only to come from elsewhere in GC code. +#define VerifyIfTrusted( exp_ ) \ + CVerifyIfTrustedHelper( CTrustedHelper_AssertOnNonPublicUniverseFailures(), (exp_), #exp_ ).GetResult() + +// !KLUDGE! Shim to make it easier to merge over stuff from DOTA +class CGCInterface +{ +public: + CSteamID ConstructSteamIDForClient( AccountID_t unAccountID ) const; +}; +extern CGCInterface *GGCInterface(); + +} // namespace GCSDK + +struct CRatelimitedSpewController +{ +public: + CRatelimitedSpewController() : m_rtCooldownStart( 0 ), m_rtLastSpew( 0 ), m_numSpewed( 0 ), m_numSkipped( 0 ) {} + ~CRatelimitedSpewController() {} + + enum ERate_t + { + k_ERate_Skip = -1, // skip printing anything, rate limit too high + k_ERate_Print = 0, // print the full message + // default is to print how many messages total as well + }; + + int RegisterSpew(); + +private: + RTime32 m_rtCooldownStart; + RTime32 m_rtLastSpew; + int m_numSpewed; + int m_numSkipped; +}; + +#define EmitErrorRatelimited( SPEW_GROUP_ID, fmtstring, ... ) \ + static CRatelimitedSpewController s_spewcontroller; \ + int nspewcontroller = s_spewcontroller.RegisterSpew(); \ + switch ( nspewcontroller ) \ + { \ + case CRatelimitedSpewController::k_ERate_Skip: break; \ + default: \ + EmitError( SPEW_GROUP_ID, nspewcontroller \ + ? fmtstring "(... and %d more skipped)\n" \ + : fmtstring \ + , __VA_ARGS__ \ + , nspewcontroller \ + ); \ + break; \ + } \ + + +#define EmitInfoRatelimited( SPEW_GROUP_ID, ConsoleLevel, LogLevel, fmtstring, ... ) \ + static CRatelimitedSpewController s_spewcontroller; \ + int nspewcontroller = s_spewcontroller.RegisterSpew(); \ + switch ( nspewcontroller ) \ + { \ + case CRatelimitedSpewController::k_ERate_Skip: break; \ + default: \ + EmitInfo( SPEW_GROUP_ID, ConsoleLevel, LogLevel, nspewcontroller \ + ? fmtstring "(... and %d more skipped)\n" \ + : fmtstring \ + , __VA_ARGS__ \ + , nspewcontroller \ + ); \ + break; \ + } \ + + +#define EG_WARNING_RATELIMITED( SPEW_GROUP_ID, fmtstring, ... ) \ + static CRatelimitedSpewController s_spewcontroller; \ + int nspewcontroller = s_spewcontroller.RegisterSpew(); \ + switch ( nspewcontroller ) \ + { \ + case CRatelimitedSpewController::k_ERate_Skip: break; \ + default: \ + EG_WARNING( SPEW_GROUP_ID, nspewcontroller \ + ? fmtstring "(... and %d more skipped)\n" \ + : fmtstring \ + , __VA_ARGS__ \ + , nspewcontroller \ + ); \ + break; \ + } \ + + + +#endif // GCBASE_H |