summaryrefslogtreecommitdiff
path: root/public/gcsdk/gcbase.h
diff options
context:
space:
mode:
Diffstat (limited to 'public/gcsdk/gcbase.h')
-rw-r--r--public/gcsdk/gcbase.h834
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