This commit is contained in:
FluorescentCIAAfricanAmerican
2020-04-22 12:56:21 -04:00
commit 3bf9df6b27
15370 changed files with 5489726 additions and 0 deletions
+165
View File
@@ -0,0 +1,165 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Holds the CAccountDetails class.
//
//=============================================================================
#ifndef ACCOUNTDETAILS_H
#define ACCOUNTDETAILS_H
#ifdef _WIN32
#pragma once
#endif
#include "tier1/thash.h"
#include "tier1/utlhashmaplarge.h"
namespace GCSDK
{
class CAccountDetails
{
public:
CAccountDetails();
void Init( CGCSystemMsg_GetAccountDetails_Response &msgResponse );
void Reset();
bool BIsExpired() const;
bool BIsValid() const { return m_bValid; }
const char *GetAccountName() const { return m_sAccountName.Get(); }
bool BHasPublicProfile() const { return m_bPublicProfile; }
bool BHasPublicInventory() const { return m_bPublicInventory; }
bool BIsVacBanned() const { return m_bVacBanned; }
bool BIsCyberCafe() const { return m_bCyberCafe; }
bool BIsSchoolAccount() const { return m_bSchoolAccount; }
bool BIsFreeTrialAccount() const { return m_bFreeTrialAccount; }
bool BIsFreeTrialAccountOrDemo() const { return m_bFreeTrialAccount || m_unPackage == 0; }
bool BIsSubscribed() const { return m_bSubscribed; }
bool BIsLowViolence() const { return m_bLowViolence; }
bool BIsLimitedAccount() const { return m_bLimited; }
bool BIsAccountLocked() const { return m_bAccountLocked; }
bool BIsCommunityBanned() const { return m_bCommunityBanned; }
bool BIsTradeBanned() const { return m_bTradeBanned; }
bool BIsSteamGuardEnabled() const { return m_bIsSteamGuardEnabled; }
bool BIsPhoneVerified() const { return m_bIsPhoneVerified; }
bool BIsTwoFactorAuthEnabled() const { return m_bIsTwoFactorAuthEnabled; }
bool BIsPhoneIdentifying() const { return m_bIsPhoneIdentifying; }
uint32 GetPackage() const { return m_unPackage; }
RTime32 GetTimeVACBanEnd() const { return m_rtimeVACBanEnd; }
uint32 GetSteamLevel() const { return m_unSteamLevel; }
uint32 GetFriendCount() const { return m_unFriendCount; }
RTime32 GetTimeAccountCreated() const { return m_rtimeAccountCreated; }
RTime32 GetTimeTwoFactorEnabled() const { return m_rtimeTwoFactorEnabled; }
RTime32 GetTimePhoneVerified() const { return m_rtimePhoneVerified; }
uint64 GetPhoneID() const { return m_unPhoneID; }
#ifdef DBGFLAG_VALIDATE
void Validate( CValidator &validator, const char *pchName );
#endif
private:
CUtlConstString m_sAccountName;
CUtlConstString m_sCurrency;
RTime32 m_rtimeCached;
uint32 m_unPackage;
RTime32 m_rtimeVACBanEnd;
uint32 m_unSteamLevel;
uint32 m_unFriendCount;
RTime32 m_rtimeAccountCreated;
RTime32 m_rtimeTwoFactorEnabled;
RTime32 m_rtimePhoneVerified;
uint64 m_unPhoneID;
bool
m_bValid:1,
m_bPublicProfile:1,
m_bPublicInventory:1,
m_bVacBanned:1,
m_bCyberCafe:1,
m_bSchoolAccount:1,
m_bFreeTrialAccount:1,
m_bSubscribed:1,
m_bLowViolence:1,
m_bLimited:1,
m_bAccountLocked:1,
m_bCommunityBanned:1,
m_bTradeBanned:1,
m_bIsSteamGuardEnabled:1,
m_bIsPhoneVerified:1,
m_bIsTwoFactorAuthEnabled:1,
m_bIsPhoneIdentifying:1;
};
class CCachedPersonaName
{
public:
CCachedPersonaName();
~CCachedPersonaName();
void Init( const char *pchPersonaName );
void Reset();
bool BIsExpired() const;
bool BIsValid() const;
bool BIsLoading() const;
void SetPreloading();
void AddLoadingRef();
void ReleaseLoadingRef();
const char *GetPersonaName() const;
#ifdef DBGFLAG_VALIDATE
void Validate( CValidator &validator, const char *pchName );
#endif
private:
CUtlConstString m_sPersonaName;
RTime32 m_rtimeCached;
int32 m_nLoading;
bool m_bPreloading;
};
//-----------------------------------------------------------------------------
// Purpose: Manages requests for CAccountDetails objects
//-----------------------------------------------------------------------------
class CAccountDetailsManager
{
public:
CAccountDetailsManager();
CAccountDetails *YieldingGetAccountDetails( const CSteamID &steamID, bool bForceReload = false );
void PreloadPersonaName( const CSteamID &steamID );
const char *YieldingGetPersonaName( const CSteamID &steamID );
void ClearCachedPersonaName( const CSteamID &steamID );
void MarkFrame();
bool BExpireRecords( CLimitTimer &limitTimer );
void Dump() const;
private:
friend class CGCJobSendGetAccountDetailsRequest;
bool BFindAccountDetailsInLocalCache( const CSteamID &steamID, CAccountDetails **ppAccount );
void WakeWaitingAccountDetailsJobs( const CSteamID &steamID );
CTHash<CAccountDetails, uint32> m_hashAccountDetailsCache;
CUtlHashMapLarge<CSteamID, CCopyableUtlVector<JobID_t> > m_mapQueuedAccountDetailsRequests;
friend class CGCJobSendGetPersonaNamesRequest;
void SendBatchedPersonaNamesRequest();
CCachedPersonaName *FindOrCreateCachedPersonaName( const CSteamID &steamID );
void CachePersonaName( const CSteamID &steamID, const char *pchPersonaName );
void CachePersonaNameFailure( const CSteamID &steamID );
void WakeWaitingPersonaNameJobs( const CSteamID &steamID );
CUtlVector<CSteamID> m_vecPendingPersonaNameLookups;
CTHash<CCachedPersonaName, uint32> m_hashPersonaNameCache;
CUtlHashMapLarge<CSteamID, CCopyableUtlVector<JobID_t> > m_mapQueuedPersonaNameRequests;
};
} // namespace GCSDK
#endif // ACCOUNTDETAILS_H
+67
View File
@@ -0,0 +1,67 @@
//========= Copyright ©, Valve LLC, All rights reserved. ======================
//
// Purpose: Defines a buffer pool used to group small allocations
//
//=============================================================================
#ifndef BUFFERPOOL_H
#define BUFFERPOOL_H
#ifdef _WIN32
#pragma once
#endif
#include "tier1/utlbuffer.h"
#include "tier1/utlvector.h"
namespace GCSDK
{
//----------------------------------------------------------------------------
// Purpose: Defines buffers that can be used to group lots of small allocs
// together to improve performance. The buffers will naturally grow over time
// to accommodate the largest consumers of the feature.
//----------------------------------------------------------------------------
class CBufferPool
{
public:
CBufferPool( const char *pchName, const GCConVar &cvMaxSizeMB, const GCConVar &cvInitBufferSize, int nFlags = 0 );
~CBufferPool();
CUtlBuffer *GetBuffer();
void ReturnBuffer( CUtlBuffer *pBuffer );
static void DumpPools();
private:
static CUtlVector<CBufferPool *> sm_vecBufferPools;
const GCConVar &m_cvMaxSizeMB;
const GCConVar &m_cvInitBufferSize;
int m_nFlags;
int32 m_nBuffersInUse;
int32 m_nHighWatermark;
int32 m_nBuffersTotal;
size_t m_cubFree;
CUtlVector<CUtlBuffer *> m_vecFreeBuffers;
CUtlConstString m_sName;
};
//thread safe version of the above which synchronizes access at the buffer allocate/release
class CBufferPoolMT
{
public:
CBufferPoolMT( const char *pchName, const GCConVar &cvMaxSizeMB, const GCConVar &cvInitBufferSize, int nFlags = 0 ) :
m_BufferPool( pchName, cvMaxSizeMB, cvInitBufferSize, nFlags )
{}
CUtlBuffer *GetBuffer() { AUTO_LOCK( m_mutex ); return m_BufferPool.GetBuffer(); }
void ReturnBuffer( CUtlBuffer *pBuffer ) { AUTO_LOCK( m_mutex ); m_BufferPool.ReturnBuffer( pBuffer ); }
private:
CBufferPool m_BufferPool;
CThreadFastMutex m_mutex;
};
} // namespace GCSDK
#endif // BUFFERPOOL_H
+166
View File
@@ -0,0 +1,166 @@
//====== Copyright ©, Valve Corporation, All rights reserved. =================
//
// Purpose: Defines a directory for all the GC processes for an app
//
//=============================================================================
#ifndef DIRECTORY_H
#define DIRECTORY_H
#ifdef _WIN32
#pragma once
#endif
#include "tier1/utlvector.h"
#include "tier1/utlsortvector.h"
#include "tier1/utlmap.h"
#include "tier1/keyvalues.h"
#include "gamecoordinator/igamecoordinator.h"
namespace GCSDK
{
class CGCDirProcess;
//-----------------------------------------------------------------------------
// Purpose: Entry for a GC instance
//-----------------------------------------------------------------------------
class CGCDirTypeInstance
{
public:
CGCDirTypeInstance( const char* pszTypeName, uint32 nType, uint32 nInstance, const KeyValues *pKVConfig, const CGCDirProcess* pProcess );
~CGCDirTypeInstance();
uint32 GetType() const;
const char* GetTypeName() const;
uint32 GetInstance() const;
KeyValues * GetConfig() const;
const CGCDirProcess* GetProcess() const;
private:
int32 m_nType;
uint32 m_nInstance;
KeyValues * m_pConfig;
CUtlConstString m_sTypeName;
const CGCDirProcess* m_pProcess;
};
//-----------------------------------------------------------------------------
// Purpose: Entry for a physical machine hosting one or more GC instances
//-----------------------------------------------------------------------------
class CGCDirProcess
{
public:
CGCDirProcess( uint32 nDirIndex, const char *pchProcessName, const char* pchProcessType, const KeyValues *pKVConfig );
~CGCDirProcess( );
//determines if this process has any GC's of the specified type
bool HasInstanceOfType( uint32 nType ) const;
uint32 GetGCDirIndex() const;
const char * GetName() const;
const char * GetProcessType() const;
uint32 GetTypeInstanceCount() const;
KeyValues * GetConfig() const;
const CGCDirTypeInstance * GetTypeInstance( uint32 nGCIndex ) const;
//gets the list of unique types contained within this process as IDs (note order does not match)
const CUtlSortVector< uint32 >& GetUniqueTypeList() const;
private:
friend class CDirectory;
void AddGC( CGCDirTypeInstance *pGC );
uint32 m_iDirGC;
CUtlConstString m_sName;
CUtlConstString m_sType;
KeyValues * m_pConfig;
CUtlVector<CGCDirTypeInstance *> m_vecGCs;
//a bit mask of which types exist on this GC
uint64 m_nTypeMask;
//optimized list for fast searching of which types this process has
CUtlSortVector< uint32 > m_vecUniqueTypeList;
};
//-----------------------------------------------------------------------------
// Purpose: Defines what kind of GCs exist for this app in the current universe,
// the types that exist, and what machines they run on
//-----------------------------------------------------------------------------
class CDirectory
{
public:
typedef CGCBase *(*GCFactory_t)( const CGCDirProcess *pGCDirProcess );
static const int s_nInvalidGCType = -1;
CDirectory();
~CDirectory();
bool BInit( KeyValues *pKVDirectory );
void RegisterGCType( int32 nTypeID, const char *pchName );
uint32 GetProcessCount() const;
const CGCDirProcess *GetProcess( int32 nIndex ) const;
uint32 GetGCTypeInstanceCount() const;
const CGCDirTypeInstance *GetGCTypeInstance( uint32 nTypeInst ) const;
int32 GetGCCountForType( int32 nTypeID ) const;
const CGCDirTypeInstance *GetGCInstanceForType( int32 nTypeID, int32 nInstance ) const;
const char *GetNameForGCType( int32 nTypeID ) const;
int32 GetGCTypeForName( const char *pchName ) const;
void RegisterProcessTypeFactory( const char* pszProcessType, GCFactory_t pfnFactory );
GCFactory_t GetFactoryForProcessType( const char* pszProcessType ) const;
//given a GC type, and an instance number, this will return the GC directory index used for routing. If the instance number
//is greater than what is available, it will be modulo'd into the acceptable range. If no match for that type can be found, -1 will
//be returned
int32 GetGCDirIndexForInstance( int32 nTypeID, uint32 nInstanceIndex ) const;
//given a GC type, this will add all of the GC indices associated with that type onto the provided vector
void GetGCDirIndicesForType( int32 nTypeID, CUtlVector< uint32 >& nIndices ) const;
private:
bool m_bInitialized;
CUtlVector<CGCDirProcess *> m_vecProcesses;
CUtlVector<CGCDirTypeInstance *> m_vecTypeInstances;
CUtlMap< int32, CCopyableUtlVector< CGCDirTypeInstance *> > m_mapGCsByType;
struct RegisteredGCType_t
{
CUtlConstString m_strName;
};
CUtlMap< int32, RegisteredGCType_t > m_mapRegisteredGCTypes;
CUtlDict< int32 > m_dictRegisteredGCNameToType;
CUtlDict< GCFactory_t > m_dictProcessTypeToFactory;
};
// Gets the global directory singleton
extern CDirectory *GDirectory();
//the maximum value that a GC type can use. This is to enable bit masks to encode type information primarily (also don't allow the high bit to avoid SQL signed issues)
#define GC_TYPE_MAX_NUMBER 62
// Registration macro for GC types
#define REG_GC_TYPE( typeNum, typeName ) \
static class CRegGC_##typeName \
{ \
public: \
CRegGC_##typeName() { \
COMPILE_TIME_ASSERT( typeNum <= GC_TYPE_MAX_NUMBER ); \
GDirectory()->RegisterGCType( ( typeNum ), ( #typeName ) ); } \
} g_RegGC_##typeName;
#define REG_GC_PROCESS_TYPE( gcClass, typeName ) \
static class CRegProcessType_##typeName \
{ \
public: \
static CGCBase *Factory( const CGCDirProcess *pDirGC ) { return new gcClass( pDirGC ); } \
CRegProcessType_##typeName() { GDirectory()->RegisterProcessTypeFactory( ( #typeName ), &Factory ); } \
} g_RegProcessType_##typeName;
} // namespace GCSDK
#endif // DIRECTORY_H
+43
View File
@@ -0,0 +1,43 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Makes enum-to-string and string-to-enum easier to declare
//
// $NoKeywords: $
//=============================================================================
#ifndef GCENUMUTILS_H
#define GCENUMUTILS_H
#ifdef _WIN32
#pragma once
#endif
namespace GCSDK
{
struct EnumString_s
{
int nValue;
const char *pszString;
};
}
// starts defining a enum value string map
#define ENUMSTRINGS_START( etype ) static const GCSDK::EnumString_s s_##etype[] = {
// ends defining a enum value string map. generates PchNameFromEnumName()
#define ENUMSTRINGS_END( etype ) }; const char* PchNameFrom##etype( etype nValue ) \
{ for( uint i=0; i<Q_ARRAYSIZE(s_##etype); i++ ) { if ( s_##etype[i].nValue == nValue ) return s_##etype[i].pszString; } \
AssertMsg2( false, "Missing String for %s (%d)", #etype, nValue ); return "Unknown"; } \
const char* PchNameFrom##etype##Unsafe( etype nValue ) \
{ for( uint i=0; i<Q_ARRAYSIZE(s_##etype); i++ ) { if ( s_##etype[i].nValue == nValue ) return s_##etype[i].pszString; } \
return NULL; }
// ends defining a enum value string map. generates PchNameFromEnum() and EnumFromNam(). Invalid element must be first in array
#define ENUMSTRINGS_REVERSE( etype, default ) }; const char* PchNameFrom##etype(etype nValue ) \
{ for( uint i=0; i<Q_ARRAYSIZE(s_##etype); i++ ) { if ( s_##etype[i].nValue == nValue ) return s_##etype[i].pszString; } \
AssertMsg2( false, "Missing String for %s (%d)", #etype, nValue ); return "Unknown"; }; \
etype etype##FromName( const char *pchName ) \
{ for( uint i=0; i<Q_ARRAYSIZE(s_##etype); i++ ) { if ( !Q_stricmp( s_##etype[i].pszString, pchName ) ) return (etype)(s_##etype[i].nValue); } \
return default; }
#endif // GCENUMUTILS_H
+141
View File
@@ -0,0 +1,141 @@
//====== Copyright (c), Valve Corporation, All rights reserved. =======
//
// Purpose: Provides a frame function manager that allows for different parts
// of a GC to hook into per frame updated
//
//=============================================================================
#ifndef FRAMEFUNCTION_H
#define FRAMEFUNCTION_H
#ifdef _WIN32
#pragma once
#endif
namespace GCSDK
{
//the generic interface for frame functions. These can be derived from and should execute the work in BRun. It returns a boolean indicating whether or not it has
//more work to complete
class CBaseFrameFunction
{
public:
//the different tiers of functions that can have functions tied to them
enum EFrameType
{
k_EFrameType_RunOnce, // called once per frame
k_EFrameType_RunUntilCompleted, // run tasks that shouldn't wait a frame between execution (gets called at least once per frame, more if work to do & we have time)
k_EFrameType_RunUntilCompletedLowPri, // as above, but only runs if k_EFrameTypeRunUntilCompleted function return they have no remaining work to do
//must come last
k_EFrameType_Count
};
CBaseFrameFunction( const char *pchName, EFrameType eFrameType );
virtual ~CBaseFrameFunction();
// runs the item
virtual bool BRun( const CLimitTimer &limitTimer ) = 0;
//called to handle registering for updates and unregistering. Not registered by default
void Register();
void Deregister();
bool IsRegistered() const { return m_bRegistered; }
protected:
//the frame function manager has access to the internals to avoid exposing them to derived classes
friend class CFrameFunctionMgr;
//let the frame function manager access internals for profiling
CUtlString m_sName; // function name (for debugging)
uint64 m_nTrackedTime; // how much time we have tracked with this frame function
uint32 m_nNumCalls; // the number of ticks that this has been associated with
EFrameType m_EFrameType; // what kind of frame function is this
bool m_bRegistered; // are we currently registered?
};
//a utility class that helps handle registering for frame functions and adapts it to a member function call
template < class T >
class CFrameFunction :
public CBaseFrameFunction
{
public:
typedef bool ( T::*func_t )( const CLimitTimer &limitTimer );
//construct and register all in one
CFrameFunction( T *pObj, func_t func, const char *pchName, EFrameType eFrameType ) :
CBaseFrameFunction( pchName, eFrameType ), m_pObj( NULL ), m_Func( NULL )
{
Register( pObj, func );
}
//construct, but don't immediately register for updates
CFrameFunction( const char *pchName, EFrameType eFrameType ) :
CBaseFrameFunction( pchName, eFrameType ), m_pObj( NULL ), m_Func( NULL )
{
}
void Register( T *pObj, func_t func )
{
m_pObj = pObj;
m_Func = func;
CBaseFrameFunction::Register();
}
virtual bool BRun( const CLimitTimer &limitTimer )
{
return (m_pObj->*m_Func)( limitTimer );
}
virtual bool BAllocedSeparately() { return false; }
private:
T *m_pObj;
func_t m_Func;
};
//the main manager that handles registration of all the frame functions and tracks performance and dispatches
//appropriately
class CFrameFunctionMgr
{
public:
CFrameFunctionMgr();
//called to register a main loop function for the specified tier
void Register( CBaseFrameFunction* pFrameFunc );
//handles unregistering a main loop function
void Deregister( CBaseFrameFunction* pFrameFunc );
//called to execute the main frame. This should preceded individual frame ticks
void RunFrame( const CLimitTimer& limitTimer );
//called within a frame for each tick
bool RunFrameTick( const CLimitTimer& limitTimer );
//called to dump a listing of the performance timings of all the frame functions that are registered
void DumpProfile();
//resets the profile stats
void ClearProfile();
private:
//sort function
static bool SortFrameFuncByTime( const CBaseFrameFunction* pLhs, const CBaseFrameFunction* pRhs );
//called to update the frame functions associated with the specified list
bool RunFrameList( CBaseFrameFunction::EFrameType eType, const CLimitTimer& limitTimer );
//the listing of subscribed
CUtlVector< CBaseFrameFunction* > m_MainLoopFrameFuncs[ CBaseFrameFunction::k_EFrameType_Count ];
//the number of frames that we have profiled
uint32 m_nNumProfileFrames;
//have we completed all of the high priority work for this frame?
bool m_bCompletedHighPri;
};
//the global frame function manager
CFrameFunctionMgr& GFrameFunctionMgr();
} //namespace GCSDK
#endif
+130
View File
@@ -0,0 +1,130 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Defines gc-specific convars that integrate their AppIDs so that
// two GC's in the same shell don' overwrite each other
//
//=============================================================================
#ifndef GCCONVAR_H
#define GCCONVAR_H
#ifdef _WIN32
#pragma once
#endif
#include "tier1/convar.h"
//-----------------------------------------------------------------------------
// Purpose: GC specifc ConVar
//-----------------------------------------------------------------------------
class GCConVar : public ConVar
{
public:
GCConVar( const char *pName, const char *pDefaultValue, const char *pHelpString, int flags = 0 )
: ConVar( pName, pDefaultValue, flags, pHelpString ) {}
GCConVar( const char *pName, const char *pDefaultValue, int flags = 0)
: ConVar( pName, pDefaultValue, flags) {}
GCConVar( const char *pName, const char *pDefaultValue, int flags, const char *pHelpString )
: ConVar( pName, pDefaultValue, flags, pHelpString ) {}
GCConVar( const char *pName, const char *pDefaultValue, int flags, const char *pHelpString, bool bMin, float fMin, bool bMax, float fMax )
: ConVar( pName, pDefaultValue, flags, pHelpString, bMin, fMin, bMax, fMax ) {}
GCConVar( const char *pName, const char *pDefaultValue, int flags, const char *pHelpString, FnChangeCallback_t callback )
: ConVar( pName, pDefaultValue, flags, pHelpString, callback ) {}
GCConVar( const char *pName, const char *pDefaultValue, int flags, const char *pHelpString, bool bMin, float fMin, bool bMax, float fMax, FnChangeCallback_t callback )
: ConVar( pName, pDefaultValue, flags, pHelpString, bMin, fMin, bMax, fMax, callback ) {}
virtual const char *GetName( void ) const;
const char *GetBaseName() const { GetName(); return m_pchBaseName; } // returns the name without the appID suffix
protected:
mutable CUtlString m_strGCName;
mutable const char *m_pchBaseName;
};
//-----------------------------------------------------------------------------
// Purpose: GC specific ConCommand
//-----------------------------------------------------------------------------
class GCConCommand : public ConCommand
{
public:
GCConCommand( const char *pName, FnCommandCallbackVoid_t callback, const char *pHelpString = 0, int flags = 0, FnCommandCompletionCallback completionFunc = 0 )
: ConCommand( pName, callback, pHelpString, flags, completionFunc ) {}
GCConCommand( const char *pName, FnCommandCallback_t callback, const char *pHelpString = 0, int flags = 0, FnCommandCompletionCallback completionFunc = 0 )
: ConCommand( pName, callback, pHelpString, flags, completionFunc ) {}
GCConCommand( const char *pName, ICommandCallback *pCallback, const char *pHelpString = 0, int flags = 0, ICommandCompletionCallback *pCommandCompletionCallback = 0 )
: ConCommand( pName, pCallback, pHelpString, flags, pCommandCompletionCallback ) {}
virtual const char *GetName( void ) const;
const char *GetBaseName() const { GetName(); return m_pchBaseName; } // returns the name without the appID suffix
protected:
mutable CUtlString m_strGCName;
mutable const char *m_pchBaseName;
};
//utility function to help identify the expected number of arguments and returns false if an inappropriate number are provided and lists the help for the con command
bool BCheckArgs( int nArgs, const CCommand &args, const ConCommandBase &command );
//utility function that will determine if the appropriate GC type is running, and if not, will print out a descriptive message about which GC type it should be run on
bool BGCConCommandVerifyGCType( uint32 nGCType );
//called to declare a console command
// !FIXME DOTAMERGE
// CCommandContext
//#define GC_CON_COMMAND( name, description ) \
// static void con_command_##name( const CCommandContext &ctx, const CCommand &args ); \
// static GCConCommand name##_command( #name, con_command_##name, description ); \
// static void con_command_##name( const CCommandContext &ctx, const CCommand &args )
#define GC_CON_COMMAND( name, description ) \
static void con_command_##name( const CCommand &args ); \
static GCConCommand name##_command( #name, con_command_##name, description ); \
static void con_command_##name( const CCommand &args )
//declares a console command that requires at least the specified number of arguments, and if not, will print out the help and not execute. For example, if you require a single
//parameter, pass in 1 for numparams. This won't block if there are additional parameters
// !FIXME DOTAMERGE
// CCommandContext
//#define GC_CON_COMMAND_PARAMS( name, numparams, description ) \
// static void con_command_##name##_ValidateParams( const CCommandContext &ctx, const CCommand &args ); \
// static GCConCommand name##_command( #name, con_command_##name##_ValidateParams, description ); \
// static void con_command_##name( const CCommandContext &ctx, const CCommand &args ); \
// static void con_command_##name##_ValidateParams( const CCommandContext &ctx, const CCommand &args ) \
// { \
// if( BCheckArgs( numparams, args, name##_command ) ) { con_command_##name( ctx, args ); } \
// } \
// static void con_command_##name( const CCommandContext &ctx, const CCommand &args )
#define GC_CON_COMMAND_PARAMS( name, numparams, description ) \
static void con_command_##name##_ValidateParams( const CCommand &args ); \
static GCConCommand name##_command( #name, con_command_##name##_ValidateParams, description ); \
static void con_command_##name( const CCommand &args ); \
static void con_command_##name##_ValidateParams( const CCommand &args ) \
{ \
if( BCheckArgs( numparams, args, name##_command ) ) { con_command_##name( args ); } \
} \
static void con_command_##name( const CCommand &args )
// Also see GC_CON_COMMAND_PARAMS_WEBAPI_ENABLED in gcwebapi.h
#define RESTRICT_GC_TYPE_CON_COMMAND( gctype ) \
{ \
if ( !BGCConCommandVerifyGCType( gctype ) ) \
return; \
} //
#define AUTO_CONFIRM_CON_COMMAND() \
{ \
static RTime32 rtimeLastRan = 0; \
if ( CRTime::RTime32TimeCur() - rtimeLastRan > 3 ) \
{ \
rtimeLastRan = CRTime::RTime32TimeCur(); \
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Auto-confirm: Please repeat command within 3 seconds to confirm.\n" ); \
return; \
} \
else \
{ \
rtimeLastRan = 0; \
} \
}
#endif
+409
View File
@@ -0,0 +1,409 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Additional shared object cache functionality for the GC
//
//=============================================================================
#ifndef GC_SHAREDOBJECTCACHE_H
#define GC_SHAREDOBJECTCACHE_H
#ifdef _WIN32
#pragma once
#endif
#include "sharedobjectcache.h"
#include "tier1/utlhashtable.h"
#include "gcdirtyfield.h"
#include "gcsdk/gcsystemaccess.h"
class CMsgSOCacheSubscribed_SubscribedType;
#include "tier0/memdbgon.h"
namespace GCSDK
{
class CCachedSubscriptionMessage;
//----------------------------------------------------------------------------
// Purpose: The part of a shared object cache that handles all objects of a
// single type.
//----------------------------------------------------------------------------
class CSharedObjectContext
{
public:
//holds information about a subscriber to the context
struct Subscriber_t
{
CSteamID m_steamID; //the steam ID of the subscriber
int m_nRefCount; //the number of references to this subscription
};
CSharedObjectContext( const CSteamID & steamIDOwner );
bool BAddSubscriber( const CSteamID & steamID );
bool BRemoveSubscriber( const CSteamID & steamID );
void RemoveAllSubscribers();
bool BIsSubscribed( const CSteamID & steamID ) const { return FindSubscriber( steamID ) != m_vecSubscribers.InvalidIndex(); }
const CUtlVector< Subscriber_t > & GetSubscribers() const { return m_vecSubscribers; }
const CSteamID & GetOwner() const { return m_steamIDOwner; }
private:
//finds a steam ID within the subscriber list, returns the index, invalid if it can't be found
int FindSubscriber( const CSteamID& steamID ) const;
CUtlVector<Subscriber_t> m_vecSubscribers;
CSteamID m_steamIDOwner;
};
class CGCSharedObjectTypeCache;
enum ESOTypeFlags
{
k_ESOFlag_SendToNobody = 0,
k_ESOFlag_SendToOwner = 1 << 0, // will go to the owner of the cache (user or gameserver), if he is subscribed
k_ESOFlag_SendToOtherUsers = 1 << 1, // will go to subscribed users who are not the owner of the cache
k_ESOFlag_SendToOtherGameservers = 1 << 2, // will go to subscribed gameservers who are not the owner of the cache
k_ESOFlag_SendToQuestObjectiveTrackers = 1 << 3,
k_ESOFlag_LastFlag = k_ESOFlag_SendToQuestObjectiveTrackers,
};
//----------------------------------------------------------------------------
// Purpose: Filter object used to determine whether a type cache's objects should
// should be sent to subscribers and whether each object should be sent
//----------------------------------------------------------------------------
class CISubscriberMessageFilter
{
public:
virtual bool BShouldSendAnyObjectsInCache( CGCSharedObjectTypeCache *pTypeCache, uint32 unFlags ) const = 0;
virtual bool BShouldSendObject( CSharedObject *pSharedObject, uint32 unFlags ) const = 0;
};
//----------------------------------------------------------------------------
// Purpose: The part of a shared object cache that handles all objects of a
// single type.
//----------------------------------------------------------------------------
class CGCSharedObjectTypeCache : public CSharedObjectTypeCache
{
public:
typedef CSharedObjectTypeCache Base;
CGCSharedObjectTypeCache( int nTypeID, const CSharedObjectContext & context );
virtual ~CGCSharedObjectTypeCache();
virtual bool AddObject( CSharedObject *pObject );
virtual CSharedObject *RemoveObject( const CSharedObject & soIndex );
inline CSteamID GetOwner() const { return m_context.GetOwner(); }
void BuildCacheSubscribedMsg( CMsgSOCacheSubscribed_SubscribedType *pMsgType, uint32 unFlags, const CISubscriberMessageFilter &filter );
virtual void EnsureCapacity( uint32 nItems );
#ifdef DBGFLAG_VALIDATE
virtual void Validate( CValidator &validator, const char *pchName );
#endif
private:
const CSharedObjectContext & m_context;
};
//----------------------------------------------------------------------------
// Purpose: A cache of a bunch of shared objects of different types. This class
// is shared between clients, gameservers, and the GC and is
// responsible for sending messages from the GC to cause object
// creation/destruction/updating on the clients/gameservers.
//----------------------------------------------------------------------------
class CGCSharedObjectCache : public CSharedObjectCache
{
public:
CGCSharedObjectCache( const CSteamID & steamIDOwner = CSteamID() );
virtual ~CGCSharedObjectCache();
const CSteamID & GetOwner() const { return m_context.GetOwner(); }
const CUtlVector< CSharedObjectContext::Subscriber_t > & GetSubscribers() const { return m_context.GetSubscribers(); }
CGCSharedObjectTypeCache *FindTypeCache( int nClassID ) const { return (CGCSharedObjectTypeCache *)FindBaseTypeCache( nClassID ); }
CGCSharedObjectTypeCache *CreateTypeCache( int nClassID ) { return (CGCSharedObjectTypeCache *)CreateBaseTypeCache( nClassID ); }
virtual uint32 CalcSendFlags( const CSteamID &steamID ) const;
virtual const CISubscriberMessageFilter &GetSubscriberMessageFilter();
virtual bool AddObject( CSharedObject *pSharedObject );
virtual bool AddObjectClean( CSharedObject *pSharedObject );
bool BDestroyObject( const CSharedObject & soIndex, bool bRemoveFromDatabase );
virtual CSharedObject *RemoveObject( const CSharedObject & soIndex );
template< typename SOClass_t >
bool BYieldingLoadSchObjects( IGCSQLResultSet *pResultSet, const CColumnSet & csRead, const SOClass_t & schDefaults );
template< typename SOClass_t >
bool BYieldingLoadSchSingleton( IGCSQLResultSet *pResultSet, const CColumnSet & csRead, const SOClass_t & schDefaults );
template< typename SOClass_t, typename SchClass_t >
bool BYieldingLoadProtoBufObjects( IGCSQLResultSet *pResultSet, const CColumnSet & csRead );
template< typename SOClass_t, typename SchClass_t >
bool BYieldingLoadProtoBufSingleton( IGCSQLResultSet *pResultSet, const CColumnSet & csRead, const SchClass_t & schDefaults );
// @todo temporary for trading and item subscriptions (to be removed once we get cross-game trading)
virtual void SetTradingPartner( const CSteamID &steamID );
const CSteamID &GetTradingPartner() const { return m_steamIDTradingPartner; }
void AddSubscriber( const CSteamID & steamID, bool bForceSendSubscriptionMsg = false );
void RemoveSubscriber( const CSteamID & steamID );
void RemoveAllSubscribers();
void SendSubscriberMessage( const CSteamID & steamID );
bool BIsSubscribed( const CSteamID & steamID ) { return m_context.BIsSubscribed( steamID ); }
void ClearCachedSubscriptionMessage();
bool BIsDatabaseDirty() const { return m_databaseDirtyList.NumDirtyObjects() > 0; }
// This will mark the field as dirty for both network and database
void DirtyObjectField( CSharedObject *pObj, int nFieldIndex );
// Marks only dirty for network
void DirtyNetworkObject( CSharedObject *pObj );
void DirtyNetworkObjectCreate( CSharedObject *pObj );
// Mark dirty for database
void DirtyDatabaseObjectField( CSharedObject *pObj, int nFieldIndex );
void SendNetworkUpdates( CSharedObject *pObj );
// Add a specific object write to a transaction. The cache is expected to remain locked until this transaction is
// closed. If the transaction is rolled back, the object will be returned to the dirty list.
bool BYieldingAddWriteToTransaction( CSharedObject *pObj, CSQLAccess & sqlAccess );
// Add all pending object writes to a transaction. The cache is expected to remain locked until this transaction is
// closed. If the transaction successfully commits, the dirty list will be flushed.
//
// This is intended for use in writeback -- no changes to the dirty objects list may occur until this transaction
// closes.
uint32 YieldingStageAllWrites( CSQLAccess & sqlAccess );
void SendAllNetworkUpdates();
void FlushInventoryCache();
void YieldingWriteToDatabase( CSharedObject *pObj );
void SetInWriteback( bool bInWriteback );
bool GetInWriteback() const { return m_bInWriteback; }
RTime32 GetWritebackTime() const { return m_unWritebackTime; }
void SetLRUHandle( uint32 unLRUHandle ) { m_unLRUHandle = unLRUHandle; }
uint32 GetLRUHandle() const { return m_unLRUHandle; }
void Dump() const;
void DumpDirtyObjects() const;
#ifdef DBGFLAG_VALIDATE
virtual void Validate( CValidator &validator, const char *pchName );
#endif
bool IsObjectCached( const CSharedObject *pObj ) const { return IsObjectCached( pObj, pObj->GetTypeID() ); }
//the same as the above, but takes in the type since if called from a destructor, you can't use virtual functions
bool IsObjectCached( const CSharedObject *pObj, uint32 nTypeID ) const;
bool IsObjectDirty( const CSharedObject *pObj ) const;
//called to mark that we are no longer loading
void SetDetectVersionChanges( bool bState ) { m_bDetectVersionChanges = bState; }
protected:
virtual CSharedObjectTypeCache *AllocateTypeCache( int nClassID ) const OVERRIDE { return new CGCSharedObjectTypeCache( nClassID, m_context ); }
virtual void MarkDirty();
virtual bool BShouldSendToAnyClients( uint32 unFlags ) const;
CCachedSubscriptionMessage *BuildSubscriberMessage( uint32 unFlags );
CSteamID m_steamIDTradingPartner;
protected:
void SendNetworkCreateInternal( CSharedObject * pObj );
void SendNetworkUpdateInternal( CSharedObject * pObj );
void SendUnsubscribeMessage( const CSteamID & steamID );
//this is a flag that when set will cause any version changes to trigger an assert. This can be used during times like loading to ensure we don't have inappropriate version changes which
//can cause inefficiencies
bool m_bDetectVersionChanges;
CSharedObjectContext m_context;
CUtlHashtable< CSharedObject * > m_networkDirtyObjs;
CUtlHashtable< CSharedObject * > m_networkDirtyObjsCreate;
CSharedObjectDirtyList m_databaseDirtyList;
bool m_bInWriteback;
RTime32 m_unWritebackTime;
uint32 m_unLRUHandle;
uint32 m_unCachedSubscriptionMsgFlags;
CCachedSubscriptionMessage *m_pCachedSubscriptionMsg;
};
//----------------------------------------------------------------------------
// Purpose: Loads a list of CSchemaSharedObjects from a result list from a
// query.
// Inputs: pResultSet - The result set from the SQL query
// schDefaults - A schema object that defines the values to set in
// the new objects for fields that were not read in the query.
// Typically this will be whatever fields were in the WHERE
// clause of the query.
// csRead - A columnSet defining the fields that were read in the query.
//----------------------------------------------------------------------------
template< typename SOClass_t >
bool CGCSharedObjectCache::BYieldingLoadSchObjects( IGCSQLResultSet *pResultSet, const CColumnSet & csRead, const SOClass_t & objDefaults )
{
if ( NULL == pResultSet )
return false;
//don't bother creating a cache if we don't have objects to add into it
if( pResultSet->GetRowCount() > 0 )
{
CGCSharedObjectTypeCache *pTypeCache = CreateTypeCache( SOClass_t::k_nTypeID );
pTypeCache->EnsureCapacity( pResultSet->GetRowCount() );
for( CSQLRecord record( 0, pResultSet ); record.IsValid(); record.NextRow() )
{
SOClass_t *pObj = new SOClass_t();
pObj->Obj() = objDefaults.Obj();
record.BWriteToRecord( &pObj->Obj(), csRead );
pTypeCache->AddObjectClean( pObj );
}
}
return true;
}
//----------------------------------------------------------------------------
// Purpose: Loads a single object of a type. If the object is not available,
// a new object will be created at default values
// Inputs: pResultSet - The result set from the SQL query
// schDefaults - A schema object that defines the values to set in
// the new objects for fields that were not read in the query.
// Typically this will be whatever fields were in the WHERE
// clause of the query.
// csRead - A columnSet defining the fields that were read in the query.
//----------------------------------------------------------------------------
template< typename SOClass_t >
bool CGCSharedObjectCache::BYieldingLoadSchSingleton( IGCSQLResultSet *pResultSet, const CColumnSet & csRead, const SOClass_t & objDefaults )
{
if ( NULL == pResultSet )
return false;
if ( pResultSet->GetRowCount() > 1 )
{
EmitError( SPEW_SHAREDOBJ, "Multiple rows passed to BYieldingLoadSchSingleton() on type %d\n", objDefaults.GetTypeID() );
return false;
}
else if ( pResultSet->GetRowCount() == 1 )
{
return BYieldingLoadSchObjects<SOClass_t>( pResultSet, csRead, objDefaults );
}
else
{
// Create it if there wasn't one
SOClass_t *pSchObj = new SOClass_t();
pSchObj->Obj() = objDefaults.Obj();
if( !pSchObj->BYieldingAddToDatabase() )
{
EmitError( SPEW_SHAREDOBJ, "Unable to add singleton type %d for %s\n", pSchObj->GetTypeID(), GetOwner().Render() );
return false;
}
AddObjectClean( pSchObj );
return true;
}
}
//----------------------------------------------------------------------------
// Purpose: Loads a list of CProtoBufSharedObjects from a result list from a
// query.
// Inputs: pResultSet - The result set from the SQL query
// schDefaults - A schema object that defines the values to set in
// the new objects for fields that were not read in the query.
// Typically this will be whatever fields were in the WHERE
// clause of the query.
// csRead - A columnSet defining the fields that were read in the query.
//----------------------------------------------------------------------------
template< typename SOClass_t, typename SchClass_t >
bool CGCSharedObjectCache::BYieldingLoadProtoBufObjects( IGCSQLResultSet *pResultSet, const CColumnSet & csRead )
{
if ( NULL == pResultSet )
return false;
//don't bother creating a cache if we don't have objects to add into it
if( pResultSet->GetRowCount() > 0 )
{
CGCSharedObjectTypeCache *pTypeCache = CreateTypeCache( SOClass_t::k_nTypeID );
pTypeCache->EnsureCapacity( pResultSet->GetRowCount() );
for( CSQLRecord record( 0, pResultSet ); record.IsValid(); record.NextRow() )
{
SchClass_t schRecord;
record.BWriteToRecord( &schRecord, csRead );
SOClass_t *pObj = new SOClass_t();
pObj->ReadFromRecord( schRecord );
pTypeCache->AddObjectClean( pObj );
}
}
return true;
}
//----------------------------------------------------------------------------
// Purpose: Loads a single object of a type. If the object is not available,
// a new object will be created at default values
// Inputs: pResultSet - The result set from the SQL query
// schDefaults - A schema object that defines the values to set in
// the new objects for fields that were not read in the query.
// Typically this will be whatever fields were in the WHERE
// clause of the query.
// csRead - A columnSet defining the fields that were read in the query.
//----------------------------------------------------------------------------
template< typename SOClass_t, typename SchClass_t >
bool CGCSharedObjectCache::BYieldingLoadProtoBufSingleton( IGCSQLResultSet *pResultSet, const CColumnSet & csRead, const SchClass_t & schDefaults )
{
if ( NULL == pResultSet )
return false;
if ( pResultSet->GetRowCount() > 1 )
{
EmitError( SPEW_SHAREDOBJ, "Multiple rows passed to BYieldingLoadProtoBufSingleton() on type %d\n", SOClass_t::k_nTypeID );
return false;
}
// load the duel summary
SchClass_t schRead;
CSQLRecord record( 0, pResultSet );
if( record.IsValid() )
{
record.BWriteToRecord( &schRead, csRead );
}
else
{
CSQLAccess sqlAccess;
if( !sqlAccess.BYieldingInsertRecord( const_cast<SchClass_t *>( &schDefaults ) ) )
return false;
schRead = schDefaults;
}
SOClass_t *pSharedObject = new SOClass_t();
pSharedObject->ReadFromRecord( schRead );
AddObjectClean( pSharedObject );
return true;
}
} // namespace GCSDK
#include "tier0/memdbgoff.h"
#endif //GC_SHAREDOBJECTCACHE_H
+834
View File
@@ -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
+99
View File
@@ -0,0 +1,99 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Holds the CGCClient class
//
//=============================================================================
#ifndef GCCLIENT_H
#define GCCLIENT_H
#ifdef _WIN32
#pragma once
#endif
#include "steam/steam_api.h"
#include "jobmgr.h"
#include "sharedobject.h"
class ISteamGameCoordinator;
struct GCMessageAvailable_t;
class CTestEvent;
namespace GCSDK
{
//-----------------------------------------------------------------------------
// Purpose: base for CGCMsgHandler
// used only by CGCMsgHandler, shouldn't be used directly
//-----------------------------------------------------------------------------
class CGCClient
{
public:
CGCClient( ISteamGameCoordinator *pSteamGameCoordinator = NULL, bool bGameserver = false );
virtual ~CGCClient( );
bool BInit( ISteamGameCoordinator *pSteamGameCoordinator );
void Uninit( );
bool BMainLoop( uint64 ulLimitMicroseconds, uint64 ulFrameTimeMicroseconds = 0 );
CJobMgr &GetJobMgr() { return m_JobMgr; }
bool BSendMessage( uint32 unMsgType, const uint8 *pubData, uint32 cubData );
bool BSendMessage( const CGCMsgBase& msg );
bool BSendMessage( const CProtoBufMsgBase& msg );
/// Locate a given shared object from the cache
CSharedObject *FindSharedObject( const CSteamID & ownerID, const CSharedObject & soIndex );
/// Find a shared object cache for the specified user. Optionally, the cache will be
/// created if it doesn't not currently exist.
CGCClientSharedObjectCache *FindSOCache( const CSteamID & steamID, bool bCreateIfMissing = true );
/// Adds a listener to the shared object cache for the specified Steam ID.
///
/// @see CGCClientSharedObjectCache::AddListener
void AddSOCacheListener( const CSteamID &ownerID, ISharedObjectListener *pListener );
/// Removes a listener for the shared object cache for the specified Steam ID.
/// Returns true if we were listening and were successfully removed, false
/// otherwise
///
/// @see CGCClientSharedObjectCache::RemoveListener
bool RemoveSOCacheListener( const CSteamID &ownerID, ISharedObjectListener *pListener );
void OnGCMessageAvailable( GCMessageAvailable_t *pCallback );
ISteamGameCoordinator *GetSteamGameCoordinator() { return m_pSteamGameCoordinator; }
virtual void Test_AddEvent( CTestEvent *pEvent ) {}
virtual void Test_CacheSubscribed( const CSteamID & steamID ) {}
void NotifySOCacheUnsubscribed( const CSteamID & ownerID );
void Dump();
#ifdef DBGFLAG_VALIDATE
static void ValidateStatics( CValidator &validator );
#endif
protected:
ISteamGameCoordinator *m_pSteamGameCoordinator;
CUtlMemory<uint8> m_memMsg;
// local job handling
CJobMgr m_JobMgr;
// Shared object caches
CUtlMap<CSteamID, CGCClientSharedObjectCache *> m_mapSOCache;
// Steam callback for getting notified about messages available. Not part of the class
// in Steam builds because we use the TestClientManager instead of steam_api.dll in Steam
#ifndef STEAM
CCallback< CGCClient, GCMessageAvailable_t, false > m_callbackGCMessageAvailable;
#endif
};
} // namespace GCSDK
#endif // GCCLIENT_H
+246
View File
@@ -0,0 +1,246 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Additional shared object cache functionality for the GC
//
//=============================================================================
#ifndef GCCLIENT_SHAREDOBJECTCACHE_H
#define GCCLIENT_SHAREDOBJECTCACHE_H
#ifdef _WIN32
#pragma once
#endif
#include "sharedobjectcache.h"
class CMsgSOCacheSubscribed;
class CMsgSOCacheSubscribed_SubscribedType;
namespace GCSDK
{
/// Enumerate different events that might trigger a callback to an ISharedObjectListener
enum ESOCacheEvent
{
/// Dummy sentinel value
eSOCacheEvent_None = 0,
/// We received a our first update from the GC and are subscribed
eSOCacheEvent_Subscribed = 1,
/// We lost connection to GC or GC notified us that we are no longer subscribed.
/// Objects stay in the cache, but we no longer receive updates
eSOCacheEvent_Unsubscribed = 2,
/// We received a full update from the GC on a cache for which we were already subscribed.
/// This can happen if connectivity is lost, and then restored before we realized it was lost.
eSOCacheEvent_Resubscribed = 3,
/// We received an incremental update from the GC about specific object(s) being
/// added, updated, or removed from the cache
eSOCacheEvent_Incremental = 4,
/// A lister was added to the cache
/// @see CGCClientSharedObjectCache::AddListener
eSOCacheEvent_ListenerAdded = 5,
/// A lister was removed from the cache
/// @see CGCClientSharedObjectCache::RemoveListener
eSOCacheEvent_ListenerRemoved = 6,
};
//----------------------------------------------------------------------------
// Purpose: Allow game components to register themselves to hear about inventory
// changes when they are received from the server
//----------------------------------------------------------------------------
class ISharedObjectListener
{
public:
/// Called when a new object is created in a cache we are currently subscribed to, or when we are added
/// as a listener to a cache which already has objects in it
///
/// eEvent will be one of:
/// - eSOCacheEvent_Subscribed
/// - eSOCacheEvent_Resubscribed
/// - eSOCacheEvent_Incremental
/// - eSOCacheEvent_ListenerAdded
virtual void SOCreated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) = 0;
/// Called when we are about to update objects in our cache but haven't done any of the work yet.
///
/// eEvent will be one of:
/// - eSOCacheEvent_Resubscribed
/// - eSOCacheEvent_Incremental
virtual void PreSOUpdate( const CSteamID & steamIDOwner, ESOCacheEvent eEvent ) = 0;
/// Called when an object is updated in a cache we are currently subscribed to.
///
/// eEvent will be one of:
/// - eSOCacheEvent_Resubscribed
/// - eSOCacheEvent_Incremental
virtual void SOUpdated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) = 0;
/// Called when we're finished updating objects in our cache for this batch.
///
/// eEvent will be one of:
/// - eSOCacheEvent_Resubscribed
/// - eSOCacheEvent_Incremental
virtual void PostSOUpdate( const CSteamID & steamIDOwner, ESOCacheEvent eEvent ) = 0;
/// Called when an object is about to be deleted in a cache we are currently subscribed to.
/// The object will have already been removed from the cache, but is still valid.
///
/// eEvent will be one of:
/// - eSOCacheEvent_Incremental
/// - eSOCacheEvent_Resubscribed
virtual void SODestroyed( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) = 0;
/// Called to notify a listener that he is subscribed to the cache.
///
/// eEvent will be one of:
/// - eSOCacheEvent_Subscribed
/// - eSOCacheEvent_Resubscribed
/// - eSOCacheEvent_ListenerAdded
///
/// A listener is guaranteed that it will not receive incremental updates (SOCreated,
/// SOUpdated, SODestroyed) while not subscribed. (Before the SOCacheSubscribed or
/// after SOCacheUnsubscribed.) However, note that it may be possible to receive
/// an SOCacheSubscribed message while already subscribed. This can happen if the
/// GC loses and restores connection, or otherwise decides that a full update is
/// necessary.
virtual void SOCacheSubscribed( const CSteamID & steamIDOwner, ESOCacheEvent eEvent ) = 0;
/// Called to notify a listener that he is no longer subscribed to the cache.
/// if he is being removed as a listener, then he will no longer receive
/// updates. Otherwise, he might receive another SOCacheSubscribed
/// message, followed by further update notifications.
///
/// eEvent will be one of:
/// - eSOCacheEvent_Unsubscribed
/// - eSOCacheEvent_ListenerRemoved
virtual void SOCacheUnsubscribed( const CSteamID & steamIDOwner, ESOCacheEvent eEvent ) = 0;
};
//----------------------------------------------------------------------------
// Purpose: The part of a shared object cache that handles all objects of a
// single type.
//----------------------------------------------------------------------------
class CGCClientSharedObjectContext
{
public:
CGCClientSharedObjectContext( const CSteamID & steamIDOwner );
bool BAddListener( ISharedObjectListener *pListener );
bool BRemoveListener( ISharedObjectListener *pListener );
void SOCreated( const CSharedObject *pObject, ESOCacheEvent eEvent ) const;
void PreSOUpdate( GCSDK::ESOCacheEvent eEvent ) const;
void SOUpdated( const CSharedObject *pObject, ESOCacheEvent eEvent ) const;
void PostSOUpdate( GCSDK::ESOCacheEvent eEvent ) const;
void SODestroyed( const CSharedObject *pObject, ESOCacheEvent eEvent ) const;
void SOCacheSubscribed( const CSteamID & steamIDOwner, ESOCacheEvent eEvent ) const;
void SOCacheUnsubscribed( const CSteamID & steamIDOwner, ESOCacheEvent eEvent ) const;
const CSteamID & GetOwner() const { return m_steamIDOwner; }
const CUtlVector< ISharedObjectListener * > & GetListeners() const { return m_vecListeners; }
private:
CSteamID m_steamIDOwner;
CUtlVector<ISharedObjectListener *> m_vecListeners;
};
//----------------------------------------------------------------------------
// Purpose: The part of a shared object cache that handles all objects of a
// single type.
//----------------------------------------------------------------------------
class CGCClientSharedObjectTypeCache : public CSharedObjectTypeCache
{
public:
CGCClientSharedObjectTypeCache( int nTypeID, const CGCClientSharedObjectContext & context );
virtual ~CGCClientSharedObjectTypeCache();
bool BParseCacheSubscribedMsg( const CMsgSOCacheSubscribed_SubscribedType & msg, CUtlVector<CSharedObject*> &vecCreatedObjects, CUtlVector<CSharedObject*> &vecUpdatedObjects, CUtlVector<CSharedObject*> &vecObjectsToDestroy );
CSharedObject *BCreateFromMsg( const void *pvData, uint32 unSize, bool *bUpdatedExisting );
bool BDestroyFromMsg( const void *pvData, uint32 unSize );
bool BUpdateFromMsg( const void *pvData, uint32 unSize );
void RemoveAllObjects( CUtlVector<CSharedObject*> &vecObjects );
private:
const CGCClientSharedObjectContext & m_context;
};
//----------------------------------------------------------------------------
// Purpose: A cache of a bunch of shared objects of different types. This class
// is shared between clients, gameservers, and the GC and is
// responsible for sending messages from the GC to cause object
// creation/destruction/updating on the clients/gameservers.
//----------------------------------------------------------------------------
class CGCClientSharedObjectCache : public CSharedObjectCache
{
friend class CGCSOUpdateMultipleJob;
public:
CGCClientSharedObjectCache( const CSteamID & steamIDOwner = CSteamID() );
virtual ~CGCClientSharedObjectCache();
/// Have we received at least one update from the GC?
bool BIsInitialized() const { return m_bInitialized; }
/// Are we currently subscribed to updates from the GC?
bool BIsSubscribed() const { return m_bSubscribed; }
/// Who owns this cache?
virtual const CSteamID & GetOwner() const { return m_context.GetOwner(); }
/// Adds a listener interface to the object. A call to re-add
/// a listener that's already listening is harmlessly ignored.
///
/// If the cache is currently subscribed, then the listener will
/// immediately receive a SOCacheSubscribed call.
void AddListener( ISharedObjectListener *pListener );
/// Removes a listener interface on the object. Returns true
/// if we were listening and removed OK, false if we were not
/// previously listening and the call was ignored
///
/// If the cache is currently subscribed and we were listening,
/// then the listener will immediately receive a SOCacheUnsubscribed
/// call.
bool RemoveListener( ISharedObjectListener *pListener );
CGCClientSharedObjectTypeCache *FindTypeCache( int nClassID ) { return (CGCClientSharedObjectTypeCache *)FindBaseTypeCache( nClassID ); }
CGCClientSharedObjectTypeCache *CreateTypeCache( int nClassID ) { return (CGCClientSharedObjectTypeCache *)CreateBaseTypeCache( nClassID ); }
bool BParseCacheSubscribedMsg( const CMsgSOCacheSubscribed & msg );
void NotifyUnsubscribe();
void NotifyResubscribedUpToDate();
bool BCreateFromMsg( int nTypeID, const void *pvData, uint32 unSize );
bool BDestroyFromMsg( int nTypeID, const void *pvData, uint32 unSize );
bool BUpdateFromMsg( int nTypeID, const void *pvData, uint32 unSize );
#ifdef DBGFLAG_VALIDATE
virtual void Validate( CValidator &validator, const char *pchName );
#endif
protected:
private:
virtual CSharedObjectTypeCache *AllocateTypeCache( int nClassID ) const OVERRIDE { return new CGCClientSharedObjectTypeCache( nClassID, m_context ); }
CGCClientSharedObjectTypeCache *GetTypeCacheByIndex( int nIndex ) { return (CGCClientSharedObjectTypeCache *)CSharedObjectCache::GetTypeCacheByIndex( nIndex ); }
CGCClientSharedObjectContext m_context;
bool m_bInitialized;
bool m_bSubscribed;
};
} // namespace GCSDK
#endif //GCCLIENT_SHAREDOBJECTCACHE_H
+139
View File
@@ -0,0 +1,139 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#ifndef GCCLIENTJOB_H
#define GCCLIENTJOB_H
#ifdef _WIN32
#pragma once
#endif
namespace GCSDK
{
class CGCClient;
//-----------------------------------------------------------------------------
// Purpose: handles a network message job from the client
//-----------------------------------------------------------------------------
class CGCClientJob : public CJob
{
public:
CGCClientJob( CGCClient *pGCClient ) : CJob( pGCClient->GetJobMgr() ), m_pGCClient( pGCClient ), m_cHeartbeatsBeforeTimeout( k_cJobHeartbeatsBeforeTimeoutDefault ) {}
// all GCClient jobs must implement one of these
virtual bool BYieldingRunGCJob( IMsgNetPacket *pNetPacket ) { return false; }
virtual bool BYieldingRunGCJob() { return false; }
virtual EServerType GetServerType() { return k_EServerTypeGCClient; }
protected:
CGCClient *m_pGCClient;
bool BYldSendMessageAndGetReply( CGCMsgBase &msgOut, uint nTimeoutSec, CGCMsgBase *pMsgIn, MsgType_t eMsg )
{
IMsgNetPacket *pNetPacket = NULL;
if ( !BYldSendMessageAndGetReply( msgOut, nTimeoutSec, &pNetPacket ) )
return false;
pMsgIn->SetPacket( pNetPacket );
if ( pMsgIn->Hdr().m_eMsg != eMsg )
return false;
return true;
}
bool BYldSendMessageAndGetReply( CGCMsgBase &msgOut, uint nTimeoutSec, IMsgNetPacket **ppNetPacket )
{
msgOut.ExpectingReply( GetJobID() );
if ( !m_pGCClient->BSendMessage( msgOut ) )
return false;
SetJobTimeout( nTimeoutSec );
return BYieldingWaitForMsg( ppNetPacket );
}
enum BYldSendMessageAndGetReply_t
{
BYLDREPLY_SUCCESS,
BYLDREPLY_SEND_FAILED,
BYLDREPLY_TIMEOUT,
BYLDREPLY_MSG_TYPE_MISMATCH,
};
BYldSendMessageAndGetReply_t BYldSendMessageAndGetReplyEx( CProtoBufMsgBase &msgOut, uint nTimeoutSec, CProtoBufMsgBase *pMsgIn, MsgType_t eMsg )
{
IMsgNetPacket *pNetPacket = NULL;
msgOut.ExpectingReply( GetJobID() );
if ( !m_pGCClient->BSendMessage( msgOut ) )
return BYLDREPLY_SEND_FAILED;
SetJobTimeout( nTimeoutSec );
if( !BYieldingWaitForMsg( &pNetPacket ) )
return BYLDREPLY_TIMEOUT;
pMsgIn->InitFromPacket( pNetPacket );
if ( pMsgIn->GetEMsg() != eMsg )
return BYLDREPLY_MSG_TYPE_MISMATCH;
return BYLDREPLY_SUCCESS;
}
bool BYldSendMessageAndGetReply( CProtoBufMsgBase &msgOut, uint nTimeoutSec, CProtoBufMsgBase *pMsgIn, MsgType_t eMsg )
{
if( BYldSendMessageAndGetReplyEx( msgOut, nTimeoutSec, pMsgIn, eMsg ) != BYLDREPLY_SUCCESS )
return false;
return true;
}
bool BYldSendMessageAndGetReply( CProtoBufMsgBase &msgOut, uint nTimeoutSec, IMsgNetPacket **ppNetPacket )
{
msgOut.ExpectingReply( GetJobID() );
if ( !m_pGCClient->BSendMessage( msgOut ) )
return false;
SetJobTimeout( nTimeoutSec );
return BYieldingWaitForMsg( ppNetPacket );
}
virtual uint32 CHeartbeatsBeforeTimeout() { return m_cHeartbeatsBeforeTimeout; }
void SetJobTimeout( uint nTimeoutSec ) { m_cHeartbeatsBeforeTimeout = 1 + ((nTimeoutSec * k_nMillion) / k_cMicroSecJobHeartbeat); }
private:
virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
{
// Protection against a NULL GCClient. Yields so the job is not deleted instantly
if ( !m_pGCClient )
{
BYieldingWaitOneFrame();
return false;
}
return BYieldingRunGCJob( pNetPacket );
}
virtual bool BYieldingRunJob( void *pvStartParam )
{
// Protection against a NULL GCClient. Yields so the job is not deleted instantly
if ( !m_pGCClient )
{
BYieldingWaitOneFrame();
return false;
}
return BYieldingRunGCJob();
}
uint32 m_cHeartbeatsBeforeTimeout;
};
} // namespace GCSDK
#endif // GCCLIENTJOB_H
+66
View File
@@ -0,0 +1,66 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: includes all the headers required for the cliend side of the GC
// SDK GC SDK. Include this in your stdafx.h
//
//=============================================================================
#ifndef GCCLIENTSDK_H
#define GCCLIENTSDK_H
#ifdef _WIN32
#pragma once
#endif
#include "gcsteamdefines.h"
#include "tier0/platform.h"
#include "steam/steamtypes.h"
#include "tier0/dbg.h"
#include "tier0/vprof.h"
#include "tier0/fasttimer.h"
#include "tier0/t0constants.h"
#include "tier1/utlmap.h"
#include "tier1/utllinkedlist.h"
#include "tier1/utlpriorityqueue.h"
#include "tier1/utlstring.h"
#include "tier1/utlbuffer.h"
#include "tier1/mempool.h"
#include "tier1/tsmempool.h"
#include "tier1/tsmultimempool.h"
#include "tier1/checksum_crc.h"
#include "tier1/fmtstr.h"
#include "vstdlib/coroutine.h"
// public stuff
#include "steam/steamclientpublic.h"
#include "misc.h"
#include "steam/isteamclient.h"
#include "steam/isteamgamecoordinator.h"
#include "steam/steam_api.h"
// stuff to include early because it is widely depended on
#include "netpacket.h"
#include "gcmsg.h"
#include "msgprotobuf.h"
#include "gcconstants.h"
#include "refcount.h"
#include "jobtime.h"
#include "messagelist.h"
#include "gclogger.h"
#include "job.h"
#include "jobmgr.h"
#include "netpacketpool.h"
#include "sharedobject.h"
#include "protobufsharedobject.h"
#include "sharedobjectcache.h"
#include "gcclient_sharedobjectcache.h"
#include "gcclient.h"
#include "gcclientjob.h"
#include "gcsystemmsgs.h"
#include "webapi_response.h"
#endif // GCCLIENTSDK_H
+202
View File
@@ -0,0 +1,202 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: declares a variety of constants
//
// $NoKeywords: $
//=============================================================================
#ifndef GCCONSTANTS_H
#define GCCONSTANTS_H
#ifdef _WIN32
#pragma once
#endif
#include "steam/steamtypes.h"
#include "tier0/t0constants.h"
#include "gamecoordinator/igcsqlquery.h"
namespace GCSDK
{
//-----------------------------------------------------------------------------
// Timing constants
//-----------------------------------------------------------------------------
// How long each frame should last
const int k_cMicroSecPerShellFrame = 50 * k_nThousand;
// How many frames per second should we have
const uint32 k_cFramesPerSec = k_nMillion / k_cMicroSecPerShellFrame;
// Task granularity -- the longest time in microseconds you should spend without yielding
const int k_cMicroSecTaskGranularity = 5 * k_nThousand;
// If a frame runs longer than it's supposed to (which they always will by at least a little),
// we subtract the overage from the next frame. This is the maximum amount to correct each frame.
const int k_cMicroSecMaxFrameCorrection = 25 * k_nThousand;
// if server time is too far behind, we will start skipping frames to re-synchronize it with real time
const int k_cMicroSecBehindToStartFrameSkipping = (k_nMillion * 2);
// Default Max time to allow a job to be blocked on I/O
const int k_cMicroSecJobPausedTimeout = 20 * k_nMillion;
// How much time a job should run before heartbeating
const int k_cMicroSecJobHeartbeat = k_cMicroSecJobPausedTimeout / 4;
// Default Max number of job heartbeat intervals to allow a job to be blocked on I/O
const int k_cJobHeartbeatsBeforeTimeoutDefault = k_cMicroSecJobPausedTimeout / k_cMicroSecJobHeartbeat;
// Number of seconds to take to cycle through all active sessions
const int k_nUserSessionRunInterval = 5 * k_nMillion;
const int k_nGSSessionRunInterval = 5 * k_nMillion;
const int k_nAccountDetailsRunInterval = 30 * k_nMillion;
const int k_nLocksRunInterval = 120 * k_nMillion;
// Time to keep an unused lock before removing it
const uint64 k_cMicroSecLockLifetime = (uint64)k_nSecondsPerHour * (uint64)k_nMillion;
//-----------------------------------------------------------------------------
// Server types
//-----------------------------------------------------------------------------
// EServerType
// Specifies the type of a specific server
// MUST BE KEPT IN SYNC with g_rgchServerTypeName !!!
enum EServerType
{
k_EServerTypeInvalid = -1,
k_EServerTypeShell = 0,
k_EServerTypeGC = 1,
k_EServerTypeGCClient = 2,
// Must be last!!!
k_EServerTypeMax = 2,
};
const EServerType k_EServerTypeFirst = k_EServerTypeShell;
//-----------------------------------------------------------------------------
// Alert constants
//-----------------------------------------------------------------------------
enum EAlertType
{
k_EAlertTypeAssert,
k_EAlertTypeInfo,
k_EAlertTypeReport,
};
//-----------------------------------------------------------------------------
// Spew / EmitEvent constants
//-----------------------------------------------------------------------------
#define SPEW_ALWAYS 1
#define SPEW_NEVER 5
#define LOG_ALWAYS 1
#define LOG_NEVER 5
//-----------------------------------------------------------------------------
// string constants
//-----------------------------------------------------------------------------
const char k_rgchUnknown[] = "Unknown";
#ifdef GC
//-----------------------------------------------------------------------------
// SQL constants
//-----------------------------------------------------------------------------
const int k_cSQLObjectNameMax = 128; // max length of a SQL identifier (column name, index name, table name ... )
const int k_cchSQLStatementTextMax = 8192; // nominal max length of a SQL statement
const uint32 k_cubRecordMax = 4 * k_nMillion; // Max size of a single record
const uint32 k_cubVarFieldMax = 1 * k_nMillion; // Max size of a variable-length field
const int k_cMaxCol = 50;
const uint32 k_cMaxBindParams = 500; // the largest number of bind parameters allowed in a single query
const uint32 k_cMaxFreeBindParamBuffers = 2000; // How many bind param buffers should we leave free before we start deleting some
const uint32 k_cMaxFreeBindWebAPIBuffers = 32; // How many WebAPI buffers should we leave free before we start deleting some
// iTable constants
const int k_iTableNil = -1; // No table at all
const int k_iFieldNil = -1; // No field at all
enum EForeignKeyAction
{
k_EForeignKeyActionNoAction = 0,
k_EForeignKeyActionCascade = 1,
k_EForeignKeyActionSetNULL = 2,
};
const char *PchNameFromEForeignKeyAction( EForeignKeyAction eForeignKeyAction );
EForeignKeyAction EForeignKeyActionFromName( const char *pchName );
const char *PchNameFromEGCSQLType( EGCSQLType eForeignKeyAction );
#ifndef SQLRETURN
typedef short SQLRETURN;
#endif // SQLRETURN
#endif // GC
//-----------------------------------------------------------------------------
// WebAPI constants
//-----------------------------------------------------------------------------
const uint32 k_cubWebAPIKey = 16; // size of the web api key
const uint32 k_cchWebAPIKeyStringMax = (k_cubWebAPIKey * 2) + 1; // size of string representing web API key (hex encoded so twice the size + NUL)
typedef unsigned char WebAPIKey_t[ k_cubWebAPIKey ];
// used to revoke WebAPI keys. Stored in the database... do not reorder.
enum EWebAPIKeyStatus
{
k_EWebAPIKeyValid = 0,
k_EWebAPIKeyRevoked = 1,
k_EWebAPIKeyInvalid = 255,
};
//-----------------------------------------------------------------------------
// Game Server constants
//-----------------------------------------------------------------------------
const uint32 k_cubGameServerToken = 16; // size of the game server identity token key
const uint32 k_cchGameServerTokenStringMax = (k_cubGameServerToken * 2) + 1; // size of string representing game server identity token (hex encoded so twice the size + NUL)
typedef unsigned char GameServerIdentityToken_t[ k_cubGameServerToken ];
//-----------------------------------------------------------------------------
// Other constants
//-----------------------------------------------------------------------------
const int k_cSmallBuff = 255; // smallish buffer
const int k_cMedBuff = 1024; // medium buffer
const int k_cubMsgSizeSmall = 512; // small sized packet
const int k_cInitialNetworkBuffers = 10; // # of network buffers to see the system with
const int k_cubMaxExpectedMsgDataSize = 5 * k_nMegabyte;// the maximum application data that we EXPECT to be sent in a single message
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define FILE_AND_LINE __FILE__ ":" TOSTRING(__LINE__)
// Default capacity for session hash tables
const int k_cGCUserSessionInit = 10 * k_nThousand; // Can grow indefinitely by this increment
const int k_cBucketGCUserSession = 100 * k_nThousand; // Fixed size
const int k_cGCGSSessionInit = 1 * k_nThousand; // Can grow indefinitely by this increment
const int k_cBucketGCGSSession = 10 * k_nThousand; // Fixed size
const int k_cAccountDetailsInit = 10 * k_nThousand; // Can grow indefinitely by this increment
const int k_cBucketAccountDetails = 100 * k_nThousand; // Fixed size
const int k_cGCLocksInit = 50 * k_nThousand; // Can grow indefinitely by this increment
const int k_cBucketGCLocks = 500 * k_nThousand; // Fixed size
const char *PchNameFromEUniverse( EUniverse eUniverse );
EUniverse EUniverseFromName( const char *pchName );
} // namespace GCSDK
#endif // GCCONSTANTS_H
+82
View File
@@ -0,0 +1,82 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Network dirty field marker for shared objects
//
//=============================================================================
#ifndef GC_DIRTYFIELD_H
#define GC_DIRTYFIELD_H
#ifdef _WIN32
#pragma once
#endif
//#include "sharedobject.h"
namespace GCSDK
{
class CSharedObject;
//----------------------------------------------------------------------------
// Purpose: Holds the set of dirty fields for this object
//----------------------------------------------------------------------------
class CSharedObjectDirtyFieldList
{
public:
CSharedObjectDirtyFieldList( CSharedObject *obj );
~CSharedObjectDirtyFieldList();
CSharedObject *Obj() const;
void DirtyField( int index );
void GetDirtyFieldSet( CUtlVector<int> &fieldSet ) const;
private:
CSharedObject *m_obj;
uint32 m_firstFieldBits;
CUtlVector<int> *m_pExtendedFields;
};
//----------------------------------------------------------------------------
// Purpose: Holds a list of dirty fields on objects
//----------------------------------------------------------------------------
class CSharedObjectDirtyList
{
public:
CSharedObjectDirtyList();
~CSharedObjectDirtyList();
void DirtyObjectField( CSharedObject *obj, int field );
int InvalidIndex() const;
int NumDirtyObjects() const;
int FindIndexByObj( const CSharedObject *pObj ) const;
bool HasElement( const CSharedObject *pObj ) const;
bool GetDirtyFieldSetByIndex( int index, CSharedObject **ppObj, CUtlVector<int> &fieldSet ) const;
bool GetDirtyFieldSetByObj( CSharedObject *pObj, CUtlVector<int> &fieldSet );
bool FindAndRemove( CSharedObject *pObj );
void RemoveAll();
#ifdef DBGFLAG_VALIDATE
void Validate( CValidator &validator, const char *pchName );
#endif
private:
CUtlVector< CSharedObjectDirtyFieldList > m_sharedObjectDirtyFieldList;
};
inline int CSharedObjectDirtyList::InvalidIndex() const
{
return m_sharedObjectDirtyFieldList.InvalidIndex();
}
inline int CSharedObjectDirtyList::NumDirtyObjects() const
{
return m_sharedObjectDirtyFieldList.Count();
}
} // GCSDK
#endif //GC_DIRTYFIELD_H
+24
View File
@@ -0,0 +1,24 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Holds a pointer to the GC's host's interface
//
//=============================================================================
#ifndef GCHOST_H
#define GCHOST_H
#ifdef _WIN32
#pragma once
#endif
#include "gamecoordinator/igamecoordinatorhost.h"
namespace GCSDK
{
extern IGameCoordinatorHost *GGCHost();
extern void SetGCHost( IGameCoordinatorHost *pHost );
} // namespace GCSDK
#endif // GCHOST_H
+181
View File
@@ -0,0 +1,181 @@
//====== Copyright ©, Valve Corporation, All rights reserved. =================
//
// Purpose: Defines the GC interface exposed to the host
//
//=============================================================================
#ifndef GCINTERFACE_H
#define GCINTERFACE_H
#ifdef _WIN32
#pragma once
#endif
#include "gamecoordinator/igamecoordinator.h"
#include "gamecoordinator/igamecoordinatorhost.h"
namespace GCSDK
{
class CGCDirProcess;
class CGCBase;
//-----------------------------------------------------------------------------
// Purpose: Defines the GC interface exposed to the host. This is here to find
// out from the host which actual GC class to instantiate, and to pass through
// all other calls
//-----------------------------------------------------------------------------
class CGCInterface : public IGameCoordinator
{
public:
CGCInterface();
~CGCInterface();
//message sources that we tag message tracking with so they can be filtered accordingly
enum EMsgSource
{
eMsgSource_System = ( 1 << 0 ),
eMsgSource_Client = ( 1 << 1 ),
eMsgSource_GC = ( 1 << 2 ),
};
// Simple accessors
IGameCoordinator *GetGC();
AppId_t GetAppID() const;
const char *GetDebugName() const;
EUniverse GetUniverse() const;
bool BIsDevMode() const;
const char *GetGCDLLPath() const;
//returns the process ID of the parent process, or 0 if it isn't specified
HANDLE GetParentProcess() const { return m_hParentProcess; }
// Exposed functions from IGameCoordinatorHost
bool BProcessSystemMessage( uint32 unGCSysMsgType, const void *pubData, uint32 cubData );
bool BSendMessageToClient( uint64 ullSteamID, uint32 unMsgType, const void *pubData, uint32 cubData );
bool BSendMessageToGC( int iGCServerIDTarget, uint32 unMsgType, const void *pubData, uint32 cubData );
void EmitSpew( const char *pchGroupName, SpewType_t spewType, int iSpewLevel, int iLevelLog, const char *pchMsg );
void AsyncSQLQuery( IGCSQLQuery *pQuery, int eSchemaCatalog );
void SetStartupComplete( bool bSuccess );
void SetShutdownComplete();
// Additional services
uint64 GenerateGID();
static uint32 GetGCDirIndexFromGID( GID_t gid );
bool BSaveConvars();
void RecordAssert( const char *pchFile, int nLine, const char *pchMessage, bool *pbShouldWriteMinidump );
CSteamID ConstructSteamIDForClient( AccountID_t unAccountID ) const;
// Implementation of IGameCoordinator
virtual bool BAsyncInit( uint32 unAppID, const char *pchDebugName, int iGCIndex, IGameCoordinatorHost *pHost ) OVERRIDE;
virtual bool BMainLoopOncePerFrame( uint64 ulLimitMicroseconds ) OVERRIDE;
virtual bool BMainLoopUntilFrameCompletion( uint64 ulLimitMicroseconds ) OVERRIDE;
virtual bool BAsyncShutdown() OVERRIDE;
virtual void Unload() OVERRIDE;
virtual void HandleMessageFromClient( uint64 ullSenderID, uint32 unMsgType, void *pubData, uint32 cubData ) OVERRIDE;
virtual void HandleMessageFromSystem( uint32 unGCSysMsgType, void *pubData, uint32 cubData ) OVERRIDE;
virtual void HandleMessageFromGC( int iGCServerIDSender, uint32 unMsgType, void *pubData, uint32 cubData ) OVERRIDE;
// Allows temporary capturing of log spew for ease of generating alerts
void StartLogCapture();
void EndLogCapture();
const CUtlVector<CUtlString> *GetLogCapture();
void ClearLogCapture();
//the current version of the GC used for reporting purposes only
uint32 GetVersion() const { return m_nVersion; }
const char* GetMachineName() const { return m_sMachineName; }
//provides access to the name of the binary for this GC for development builds. This is blank for non-development builds
const char* GetDevBinaryName() const { return m_sDevBinaryName; }
//access to stats recorded about various asserts triggered in the system
struct AssertInfo_t
{
//the line the assert fired from
uint32 m_nLine;
//how many actually fired and recorded
uint32 m_nTotalFired;
uint32 m_nTotalRecorded;
//how many fired since the last clear
uint32 m_nWindowFired;
//the string associated with the first firing
CUtlString m_sMsg;
//the times we recorded last
CUtlVector< RTime32 > m_vRecordTimes;
};
typedef CUtlDict< CUtlVector< AssertInfo_t* >* > AssertInfoDict_t;
const AssertInfoDict_t& GetAssertInfo() const { return m_dictAsserts; }
//called to reset the assert window, this is useful for tracking how many asserts fired within a specific time range
void ClearAssertWindowCounts();
//called to add a substring that console output will be filtered against. This is only intended for urgent text squelching
void AddBlockEmitString( const char* pszStr, bool bBlockConsole, bool bBlockLog );
void ClearBlockEmitStrings();
//asserts are always rate limited. If you absolutely need to avoid that, you can use this object to force asserts to be recorded regardless of rate limiting
class CDisableAssertRateLimit
{
public:
CDisableAssertRateLimit() { s_nDisabledCount++; }
~CDisableAssertRateLimit() { s_nDisabledCount--; }
static int32 s_nDisabledCount;
};
private:
//the list of substrings we want to filter text based upon
struct BlockString_t
{
CUtlString m_sStr;
bool m_bBlockLog;
bool m_bBlockConsole;
};
CUtlVector< BlockString_t* > m_BlockEmitStrings;
//called to clear all existing assert stats
void ClearAssertInfo();
//this will handle loading the config file for the GC and initializing the directory, and will return the config key values
//so that it can be used to load the convars from
bool BReadConfigDirectory( KeyValuesAD& configValues );
//handles loading in the convars from the key values loaded with BReadConfigDirectory, and will setup the convars based upon
//what GC type this is
bool BReadConvars( KeyValuesAD& configValues );
void InitConVars( KeyValues *pkvConvars );
IGameCoordinatorHost *m_pGCHost;
CGCBase *m_pGC;
const CGCDirProcess *m_pGCDirProcess;
//the version of the GC. This is for reporting purposes only, and will be zero for development builds
uint32 m_nVersion;
AppId_t m_nAppID;
CUtlConstString m_sDebugName;
EUniverse m_eUniverse;
bool m_bDevMode;
CUtlConstString m_sGCDLLPath;
uint64 m_ullGID;
HANDLE m_hParentProcess;
//all the asserts that have fired
AssertInfoDict_t m_dictAsserts;
CUtlVector<CUtlString> m_vecLogCapture;
bool m_bLogCaptureEnabled;
//the name of the binary, set for development build
CUtlString m_sDevBinaryName;
//the name of the machine we are running on
CUtlString m_sMachineName;
};
extern CGCInterface *GGCInterface();
} // namespace GCSDK
#endif // GCINTERFACE_H
+241
View File
@@ -0,0 +1,241 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#ifndef GCJOB_H
#define GCJOB_H
#ifdef _WIN32
#pragma once
#endif
#include "gcwebapikey.h"
namespace GCSDK
{
//a free standing utility function that takes in a newly allocated job, and tells the job to start executing. An example of this would be pStore = StartNewJobDelayed( new CFooJob( Params ) );
template < typename T >
inline typename T* StartNewJobDelayed( T* pNewJob )
{
if ( pNewJob )
pNewJob->StartJobDelayed( NULL );
return pNewJob;
}
//-----------------------------------------------------------------------------
// Purpose: Control access to web api accounts so that they can be rate limited/blocked/exempt, etc
//-----------------------------------------------------------------------------
enum EWebAPIAccountLevel
{
eWebAPIAccountLevel_RateLimited = 0, //default, rate limit
eWebAPIAccountLevel_Blocked = 1, //block all requests
eWebAPIAccountLevel_Unlimited = 2, //do not rate limit
eWebAPIAccountLevel_Elevated = 3, //like rate limiting, but at a higher threshold
};
//resets all accounts to the default permission
void WebAPIAccount_ResetAllPermissions();
//called to associate a permission level with an account
void WebAPIAccount_SetPermission( AccountID_t nID, EWebAPIAccountLevel eLevel );
//external calling interface in case we want to use this from a non-WebAPI job
bool WebAPIAccount_BTrackUserAndValidate( AccountID_t nID, uint32 unIP );
//-----------------------------------------------------------------------------
// Purpose: handles a network message job from the client
//-----------------------------------------------------------------------------
class CGCJob : public CJob
{
public:
// Constructor: when overriding job name a static string pointer must be used
CGCJob( CGCBase *pGC, const char *pchJobName = NULL ) : CJob( pGC->GetJobMgr(), pchJobName ), m_pGC( pGC ), m_cHeartbeatsBeforeTimeout( k_cJobHeartbeatsBeforeTimeoutDefault ) {}
// all GC jobs must implement one of these
virtual bool BYieldingRunGCJob( IMsgNetPacket *pNetPacket ) { return false; }
virtual bool BYieldingRunGCJob() { return false; }
virtual EServerType GetServerType() { return k_EServerTypeGC; }
bool BYldSendMessageAndGetReply( CSteamID &steamIDTarget, CGCMsgBase &msgOut, uint nTimeoutSec, CGCMsgBase *pMsgIn, MsgType_t eMsg );
bool BYldSendMessageAndGetReply( CSteamID &steamIDTarget, CGCMsgBase &msgOut, uint nTimeoutSec, IMsgNetPacket **ppNetPacket );
bool BYldSendMessageAndGetReply( CSteamID &steamIDTarget, CProtoBufMsgBase &msgOut, uint nTimeoutSec, CProtoBufMsgBase *pMsgIn, MsgType_t eMsg );
bool BYldSendMessageAndGetReply( CSteamID &steamIDTarget, CProtoBufMsgBase &msgOut, uint nTimeoutSec, IMsgNetPacket **ppNetPacket );
virtual uint32 CHeartbeatsBeforeTimeout() { return m_cHeartbeatsBeforeTimeout; }
void SetJobTimeout( uint nTimeoutSec ) { m_cHeartbeatsBeforeTimeout = 1 + ( ( nTimeoutSec * k_nMillion - 1 ) / k_cMicroSecJobHeartbeat ); }
protected:
CGCBase *m_pGC;
private:
virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
{
return BYieldingRunGCJob( pNetPacket );
}
virtual bool BYieldingRunJob( void *pvStartParam )
{
return BYieldingRunGCJob();
}
uint32 m_cHeartbeatsBeforeTimeout;
};
//This template class is designed to be derived from various job types and provides an adapter for the run from message hook, and handles
//converting it over to an appropriate protobuf message for the caller as well as validating that it parsed properly. It will bail on processing
//the message if the protobuf does not parse.
template < typename TGCJobClass, typename TGCType, typename TProtoMsgClass >
class TProtoMsgJob :
public TGCJobClass
{
public:
typedef CProtoBufMsg< TProtoMsgClass > TProtoMsg;
TProtoMsgJob( TGCType* pGC ) : TGCJobClass( pGC ) {}
virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket ) OVERRIDE
{
TProtoMsg msg( pNetPacket );
if ( !msg.Body().IsInitialized() )
return false;
return BYieldingRunJobFromProtoMsg( msg );
}
virtual bool BYieldingRunJobFromProtoMsg( const TProtoMsg& ProtoMsg ) = 0;
};
//a partial specialization of the proto message job to make it easier to use with GCJob
template < typename TProtoMsgClass >
class CGCProtoJob : public TProtoMsgJob < CGCJob, CGCBase, TProtoMsgClass >
{
public:
CGCProtoJob( CGCBase* pGC ) : TProtoMsgJob( pGC ) {}
//clients need to implement BYieldingRunJobFromProtoMsg
};
//-----------------------------------------------------------------------------
// CGCWGJob - A job invoked from forwarded WG messages
//-----------------------------------------------------------------------------
class CGCWGJob : public CGCJob
{
public:
CGCWGJob( CGCBase *pGCBase );
~CGCWGJob();
bool BYieldingRunGCJob( IMsgNetPacket * pNetPacket ); // invokes BYieldingRunJobFromRequest
virtual bool BYieldingRunJobFromRequest( KeyValues *pkvRequest, KeyValues *pkvResponse ) = 0;
virtual bool BVerifyParams( const CGCMsg<MsgGCWGRequest_t> & msg, KeyValues *pkvRequest, const WebApiFunc_t * pWebApiFunc );
void SetWebApiFunc( const WebApiFunc_t * pWebApiFunc ) { m_pWebApiFunc = pWebApiFunc; }
void SetErrorMessage( KeyValues *pkvErr, const char *pchErrorMsg, int32 nResult );
protected:
KeyValues *m_pkvResponse;
CUtlBuffer m_bufRequest; // packed binary form of the request
const WebApiFunc_t * m_pWebApiFunc;
// requester auth info, like WGRequestContext
CSteamID m_steamID;
};
//-----------------------------------------------------------------------------
// CGCJobVerifySession - A job that asks steam if a given user is still connected
// and cleans up the session if the user is gone
//-----------------------------------------------------------------------------
class CGCJobVerifySession : public CGCJob
{
public:
CGCJobVerifySession( CGCBase *pGC, const CSteamID &steamID ) : CGCJob( pGC ), m_steamID( steamID ) { }
virtual bool BYieldingRunGCJob();
private:
CSteamID m_steamID;
};
//-----------------------------------------------------------------------------
// CWebAPIJob - A job invoked from a forwarded WebAPI request
//-----------------------------------------------------------------------------
class CWebAPIJob : public CGCJob
{
public:
CWebAPIJob( CGCBase *pGC, EWebAPIOutputFormat eDefaultOutputFormat = k_EWebAPIOutputFormat_JSON );
~CWebAPIJob();
// Called by jobmgr, and then invokes appropriate run function for specific API version requested
bool BYieldingRunJobFromMsg( IMsgNetPacket * pNetPacket );
// Implemented by each individual WebAPI job, should be declared using BEGIN_WEBAPI_JOB_VERSION_ADAPTERS and related macros.
virtual bool BYieldingRunJobFromAPIRequest( const char *pchInterface, const char *pchMethod, uint32 unVersion, CHTTPRequest *pRequest, CHTTPResponse *pResponse, CWebAPIResponse *pWebAPIResponse ) = 0;
protected:
static void AddLocalizedString( CWebAPIValues *pOutDefn, const char *pchFieldName, const char *pchKeyName, ELanguage eLang, bool bReturnTokenIfNotFound = true );
static void ThreadedEmitFormattedOutputWrapper( CWebAPIResponse *pResponse, EWebAPIOutputFormat eFormat, CUtlBuffer *poutputBuffer, size_t unMaxResultSize, bool *pbResult );
CWebAPIKey m_webAPIKey;
EWebAPIOutputFormat m_eDefaultOutputFormat;
};
#define BEGIN_WEBAPI_JOB_VERSION_ADAPTERS( interface, method ) \
bool BYieldingRunJobFromAPIRequest( const char *pchInterface, const char *pchMethod, uint32 unVersion, CHTTPRequest *pRequest, CHTTPResponse *pResponse, CWebAPIResponse *pWebAPIResponse ) \
{ \
if ( Q_strnicmp( pchInterface, interface, Q_strlen( interface ) ) != 0 ) \
{ \
AssertMsg2( false, "WebAPIJob recieved request for unexpected interface (got %s, expected %s)!", pchInterface, interface ); \
pResponse->SetStatusCode( k_EHTTPStatusCode500InternalServerError ); \
return false; \
} \
\
if ( Q_stricmp( pchMethod, method ) != 0 ) \
{ \
AssertMsg2( false, "WebAPIJob received request fo unexpected method (got %s, expected %s)!", pchMethod, method ); \
pResponse->SetStatusCode( k_EHTTPStatusCode500InternalServerError ); \
return false; \
} \
\
\
bool bFoundVersion = false; \
bool bResult = false; \
switch( unVersion ) \
{
#define WEBAPI_JOB_VERSION_ADAPTER( version, funcname ) \
case version: \
bFoundVersion = true; \
{ \
VPROF_BUDGET( #funcname, VPROF_BUDGETGROUP_JOBS_COROUTINES ); \
bResult = this->##funcname( pRequest, pWebAPIResponse ); \
} \
break;
#define WEBAPI_JOB_VERSION_ADAPTER_EXTENDED_ARRAYS( version, funcname ) \
case version: \
bFoundVersion = true; \
pWebAPIResponse->SetExtendedArrays( true ); \
{ \
VPROF_BUDGET( #funcname, VPROF_BUDGETGROUP_JOBS_COROUTINES ); \
bResult = this->##funcname( pRequest, pWebAPIResponse ); \
} \
break;
#define END_WEBAPI_JOB_VERSION_ADAPTERS() \
default: \
break; \
} \
AssertMsg3( bFoundVersion, "WebAPIJob for %s/%s received unhandled version %d", pchInterface, pchMethod, unVersion ); \
return bResult; \
}
} // namespace GCSDK
#endif // GCJOB_H
+40
View File
@@ -0,0 +1,40 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: API to interact with Steam leaderboards on the GC.
//
// $NoKeywords: $
//=============================================================================
#ifndef GCLEADERBOARDAPI_H
#define GCLEADERBOARDAPI_H
namespace GCSDK
{
class CGCBase;
enum { kInvalidLeaderboardID = 0 };
/**
* Yielding call that attempts to find a leaderboard by name, creating one if necessary.
* @param pName
* @param eLeaderboardSortMethod
* @param eLeaderboardDisplayType
* @param bCreateIfNotFound
* @return 0 if the leaderboard was not found, > 0 otherwise
*/
uint32 Leaderboard_YieldingFind( const char *pName, ELeaderboardSortMethod eLeaderboardSortMethod, ELeaderboardDisplayType eLeaderboardDisplayType, bool bCreateIfNotFound );
/**
* Yielding call that attempts to set the score for the steamID in the leaderboard.
* @param unLeaderboardID
* @param steamID
* @param eLeaderboardUploadScoreMethod
* @param score
* @param pDetails
* @param unDetailsLength
* @return true if successful, false otherwise.
*/
bool Leaderboard_YieldingSetScore( uint32 unLeaderboardID, const CSteamID &steamID, ELeaderboardUploadScoreMethod eLeaderboardUploadScoreMethod, int score );
}; // namespace GCSDK
#endif // GCLEADERBOARDAPI_H
+131
View File
@@ -0,0 +1,131 @@
//========= Copyright (c), Valve LLC, All rights reserved. ============
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================
#ifndef GCLOGGER_H
#define GCLOGGER_H
#ifdef GC
#include "gc_convar.h"
#endif
namespace GCSDK
{
//a class that defines an output group for messages that can be output to the console or to a log. This allows for individual filtering of different groups to different
//outputs to control log spamming
class CGCEmitGroup
{
public:
//the different severity levels for a message
enum EMsgLevel
{
kMsg_Error = 1,
kMsg_Warning = 2,
kMsg_Msg = 3,
kMsg_Verbose = 4
};
//constructs a group given a static string for the name of the group, and the name for the console variables that control the console and log output levels. Note
//that to help with consistency, DECLARE_GC_EMIT_GROUP should be used for declaring these objects over manually providing names for all fields
CGCEmitGroup( const char* pszGroupName, const char* pszConsoleVar, const char* pszLogVar, const char* pszDefaultConsole, const char* pszDefaultLog )
: m_pszGroupName( pszGroupName )
#ifdef GC
, m_LogLevel( pszLogVar, pszDefaultLog, 0, "The output level to log for this system. 1 means log only critical errors, 2 means log errors and warnings, 3 means log errors, warnings and messages, 4 means log everything", true, 0.0f, true, 4.0f )
, m_ConsoleLevel( pszConsoleVar, pszDefaultConsole, 0, "The output level to log for this system. 1 means log only critical errors, 2 means log errors and warnings, 3 means log errors, warnings and messages, 4 means log everything", true, 0.0f, true, 4.0f )
#endif
{}
//get the name of the group and current filter levels
const char* GetName() const { return m_pszGroupName; }
#ifdef GC
int GetConsoleLevel() const { return m_ConsoleLevel.GetInt(); }
int GetLogLevel() const { return m_LogLevel.GetInt(); }
#else
int GetConsoleLevel() const { return 3; }
int GetLogLevel() const { return 3; }
#endif
//these will output text for each of the appropriate severities, from Verbose (defaulted to filtered out), to critical error. These are not intended to be called directly, as the preparation
//of all the command line arguments can be costly, and the EG_XXXX macros should be used instead to avoid this cost.
void Internal_AssertError( PRINTF_FORMAT_STRING const char *pchMsg, ... ) const FMTFUNCTION( 2, 3 );
void Internal_Error( PRINTF_FORMAT_STRING const char *pchMsg, ... ) const FMTFUNCTION( 2, 3 );
void Internal_Warning( PRINTF_FORMAT_STRING const char *pchMsg, ... ) const FMTFUNCTION( 2, 3 );
void Internal_Msg( PRINTF_FORMAT_STRING const char *pchMsg, ... ) const FMTFUNCTION( 2, 3 );
void Internal_Verbose( PRINTF_FORMAT_STRING const char *pchMsg, ... ) const FMTFUNCTION( 2, 3 );
void Internal_Emit( EMsgLevel eLvl, PRINTF_FORMAT_STRING const char *pchMsg, ... ) const FMTFUNCTION( 3, 4 );
void Internal_BoldMsg( PRINTF_FORMAT_STRING const char *pchMsg, ... ) const FMTFUNCTION( 2, 3 );
//same as the above, but takes a var args structure
void AssertErrorV( PRINTF_FORMAT_STRING const char *pchMsg, va_list vaArgs ) const;
void ErrorV( PRINTF_FORMAT_STRING const char *pchMsg, va_list vaArgs ) const;
void WarningV( PRINTF_FORMAT_STRING const char *pchMsg, va_list vaArgs ) const;
void MsgV( PRINTF_FORMAT_STRING const char *pchMsg, va_list vaArgs ) const;
void VerboseV( PRINTF_FORMAT_STRING const char *pchMsg, va_list vaArgs ) const;
void EmitV( EMsgLevel eLvl, PRINTF_FORMAT_STRING const char *pchMsg, va_list vaArgs ) const;
void BoldMsgV( PRINTF_FORMAT_STRING const char *pchMsg, va_list vaArgs ) const;
private:
//the display name of this group, must be a static string
const char* m_pszGroupName;
#ifdef GC
//the console variable used to control the log level that will be recorded to the ouput logs [0..4]
GCConVar m_LogLevel;
//the console variable used to control the level of output that will be displayed to the console [0..4]
GCConVar m_ConsoleLevel;
#endif
};
//macros to emit to an emit group. These should be used so that you don't have to pay the cost of formatting parameters that won't be used if the output isn't turned on anyway.
//Note the use of do{}while, this to prevent the if from pairing with a following else if they don't use braces around the print
#define EG_ASSERTERROR( EmitGroup, ... ) do{ if( EmitGroup.GetConsoleLevel() >= CGCEmitGroup::kMsg_Error || EmitGroup.GetLogLevel() >= CGCEmitGroup::kMsg_Error ) EmitGroup.Internal_AssertError( __VA_ARGS__ ); } while(0)
#define EG_ERROR( EmitGroup, ... ) do{ if( EmitGroup.GetConsoleLevel() >= CGCEmitGroup::kMsg_Error || EmitGroup.GetLogLevel() >= CGCEmitGroup::kMsg_Error ) EmitGroup.Internal_Error( __VA_ARGS__ ); } while(0)
#define EG_WARNING( EmitGroup, ... ) do{ if( EmitGroup.GetConsoleLevel() >= CGCEmitGroup::kMsg_Warning || EmitGroup.GetLogLevel() >= CGCEmitGroup::kMsg_Warning ) EmitGroup.Internal_Warning( __VA_ARGS__ ); } while(0)
#define EG_MSG( EmitGroup, ... ) do{ if( EmitGroup.GetConsoleLevel() >= CGCEmitGroup::kMsg_Msg || EmitGroup.GetLogLevel() >= CGCEmitGroup::kMsg_Msg ) EmitGroup.Internal_Msg( __VA_ARGS__ ); } while(0)
#define EG_VERBOSE( EmitGroup, ... ) do{ if( EmitGroup.GetConsoleLevel() >= CGCEmitGroup::kMsg_Verbose || EmitGroup.GetLogLevel() >= CGCEmitGroup::kMsg_Verbose ) EmitGroup.Internal_Verbose( __VA_ARGS__ ); } while(0)
#define EG_EMIT( EmitGroup, eLvl, ... ) do{ if( EmitGroup.GetConsoleLevel() >= eLvl || EmitGroup.GetLogLevel() >= eLvl ) EmitGroup.Internal_Emit( eLvl, __VA_ARGS__ ); } while(0)
#define EG_BOLDMSG( EmitGroup, ... ) do{ if( EmitGroup.GetConsoleLevel() >= CGCEmitGroup::kMsg_Error || EmitGroup.GetLogLevel() >= CGCEmitGroup::kMsg_Error ) EmitGroup.Internal_BoldMsg( __VA_ARGS__ ); } while(0)
//----------------------------------------
//Legacy interface support - Where possible, use the CGCEmitGroup above for more robust functionality
//----------------------------------------
extern CGCEmitGroup SPEW_SYSTEM_MISC;
extern CGCEmitGroup SPEW_JOB;
extern CGCEmitGroup SPEW_CONSOLE;
extern CGCEmitGroup SPEW_GC;
extern CGCEmitGroup SPEW_SQL;
extern CGCEmitGroup SPEW_NETWORK;
extern CGCEmitGroup SPEW_SHAREDOBJ;
extern CGCEmitGroup SPEW_MICROTXN;
extern CGCEmitGroup SPEW_PROMO;
extern CGCEmitGroup SPEW_PKGITEM;
extern CGCEmitGroup SPEW_ECONOMY;
extern CGCEmitGroup SPEW_THREADS;
//this one is a macro since it is called in many places and can add considerable overhead to the formatting
void EGInternal_EmitInfo( const CGCEmitGroup& Group, int iLevel, int iLevelLog, PRINTF_FORMAT_STRING const char *pchMsg, ... ) FMTFUNCTION( 4, 5 );
#define EmitInfo( EmitGroup, ConsoleLevel, LogLevel, ... ) do{ if( EmitGroup.GetConsoleLevel() >= ( ConsoleLevel ) || EmitGroup.GetLogLevel() >= ( LogLevel ) ) EGInternal_EmitInfo( EmitGroup, ConsoleLevel, LogLevel, __VA_ARGS__ ); } while(0)
void EmitInfoV( const CGCEmitGroup& Group, int iLevel, int iLevelLog, PRINTF_FORMAT_STRING const char *pchMsg, va_list vaArgs );
void EmitWarning( const CGCEmitGroup& Group, int iLevel, PRINTF_FORMAT_STRING const char *pchMsg, ... ) FMTFUNCTION( 3, 4 );
void EmitError( const CGCEmitGroup& Group, PRINTF_FORMAT_STRING const char *pchMsg, ... ) FMTFUNCTION( 2, 3 );
// Emit an assert-like error, generating a minidump
void EmitAssertError( const CGCEmitGroup& Group, PRINTF_FORMAT_STRING const char *pchMsg, ... ) FMTFUNCTION( 2, 3 );
} // namespace GCSDK
//the use of the typedef is to catch issues with people putting quotes around the group name. Note that the emit group comes first in case they precede this with 'static'
#define DECLARE_GC_EMIT_GROUP_DEFAULTS( VarName, GroupName, ConsoleLevel, LogLevel ) \
GCSDK::CGCEmitGroup VarName( #GroupName, "console_level_" #GroupName, "log_level_" #GroupName, #ConsoleLevel, #LogLevel ); \
typedef uint32 __TEmitGroupSanityCheck_##GroupName_##ConsoleLevel_##LogLevel;
//a utility macro that assists in creating emit groups in a name consistent manner
#define DECLARE_GC_EMIT_GROUP( VarName, GroupName ) DECLARE_GC_EMIT_GROUP_DEFAULTS( VarName, GroupName, 3, 3 )
#endif // GCLOGGER_H
+234
View File
@@ -0,0 +1,234 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: This file defines all of the Game Coordinator messages for the
// current IS-embedded implementation of the GC
//
//=============================================================================
#ifndef GCMSG_H
#define GCMSG_H
#ifdef _WIN32
#pragma once
#endif
#include "msgbase.h"
#include "messagelist.h"
#pragma pack( push, 1 )
namespace GCSDK
{
//-----------------------------------------------------------------------------
// Purpose: Header for messages from a client or gameserver to or from the GC
//-----------------------------------------------------------------------------
struct GCMsgHdr_t
{
MsgType_t m_eMsg; // The message type
uint64 m_ulSteamID; // User's SteamID
CUtlString GetHeaderDescription();
const char *PchMsgName( ) const { return PchMsgNameFromEMsg( m_eMsg ); }
};
//-----------------------------------------------------------------------------
// Purpose: Header for messages from a client or gameserver to or from the GC
// That contains source and destination jobs for the purpose of
// replying messages.
//-----------------------------------------------------------------------------
struct GCMsgHdrEx_t
{
MsgType_t m_eMsg; // The message type
uint64 m_ulSteamID; // User's SteamID
uint16 m_nHdrVersion;
JobID_t m_JobIDTarget;
JobID_t m_JobIDSource;
CUtlString GetHeaderDescription();
const char *PchMsgName( ) const { return PchMsgNameFromEMsg( m_eMsg ); }
};
#pragma pack( push, 1 )
struct ProtoBufMsgHeader_t
{
int32 m_EMsgFlagged; // High bit should be set to indicate this message header type is in use. The rest of the bits indicate message type.
uint32 m_cubProtoBufExtHdr; // Size of the extended header which is a serialized protobuf object. Indicates where it ends and the serialized body protobuf begins.
ProtoBufMsgHeader_t() : m_EMsgFlagged( 0 ), m_cubProtoBufExtHdr( 0 ) {}
ProtoBufMsgHeader_t( MsgType_t eMsg, uint32 cubProtoBufExtHdr ) : m_EMsgFlagged( eMsg | k_EMsgProtoBufFlag ), m_cubProtoBufExtHdr( cubProtoBufExtHdr ) {}
const char *PchMsgName() const { return PchMsgNameFromEMsg( GetEMsg() ); }
MsgType_t GetEMsg() const { return (MsgType_t)(m_EMsgFlagged & (~k_EMsgProtoBufFlag) ); }
};
#pragma pack(pop)
//-----------------------------------------------------------------------------
// CStructNetPacket
// Thin wrapper around raw CNetPacket which implements our IMsgNetPacket interface.
//-----------------------------------------------------------------------------
class CStructNetPacket : public IMsgNetPacket
{
#ifdef GC
DECLARE_CLASS_MEMPOOL( CStructNetPacket );
#endif
public:
CStructNetPacket( CNetPacket *pNetPacket )
{
m_pHeader = (GCMsgHdrEx_t*)pNetPacket->PubData();
m_pNetPacket = pNetPacket;
m_pNetPacket->AddRef();
}
EMsgFormatType GetEMsgFormatType() const { return k_EMsgFormatTypeStruct; }
CNetPacket *GetCNetPacket() const { return m_pNetPacket; }
uint8 *PubData() const { return m_pNetPacket->PubData(); }
uint CubData() const { return m_pNetPacket->CubData(); }
MsgType_t GetEMsg() const { return (MsgType_t)m_pHeader->m_eMsg; }
JobID_t GetSourceJobID() const { return m_pHeader->m_JobIDSource; }
JobID_t GetTargetJobID() const { return m_pHeader->m_JobIDTarget; }
void SetTargetJobID( JobID_t ulJobID ) { m_pHeader->m_JobIDTarget = ulJobID; }
CSteamID GetSteamID() const { return CSteamID( m_pHeader->m_ulSteamID ); }
void SetSteamID( CSteamID steamID ) { m_pHeader->m_ulSteamID = steamID.ConvertToUint64(); }
AppId_t GetSourceAppID() const { return k_uAppIdInvalid; }
void SetSourceAppID( AppId_t appId ) {}
// Routing to a job name is not permitted with the old packet format
virtual bool BHasTargetJobName() const { return false; }
virtual const char *GetTargetJobName() const { return NULL; }
private:
virtual ~CStructNetPacket()
{
m_pNetPacket->Release();
}
CNetPacket *m_pNetPacket;
GCMsgHdrEx_t *m_pHeader;
};
#define GCMSG_EX_HEADER_SIZE ( sizeof( GCSDK::GCMsgHdrEx_t ) - sizeof( GCSDK::GCMsgHdr_t ) )
static const uint16 k_nHdrVersion = 0x1;
//-----------------------------------------------------------------------------
// Purpose: Header for messages from one GC to another
//-----------------------------------------------------------------------------
struct GCMsgInterHdr_t
{
MsgType_t m_eMsg; // The message type
uint32 m_unSourceAppId; // App ID of the source GC
uint16 m_nHdrVersion;
JobID_t m_JobIDTarget;
JobID_t m_JobIDSource;
CUtlString GetHeaderDescription();
const char *PchMsgName( ) const { return PchMsgNameFromEMsg( m_eMsg ); }
};
#pragma pack( pop )
//-----------------------------------------------------------------------------
// CGCMsgBase
// Message class for messages between the GC and a GS or client
// Handles a message with a header of type GCMsgHdrEx_t, a payload of type MSG_BODY_TYPE, and optional variable length data
//-----------------------------------------------------------------------------
typedef CMsgBase_t<GCMsgHdrEx_t> CGCMsgBase;
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
template <typename MSG_BODY_TYPE>
class CGCMsg : public CGCMsgBase
{
public:
// Client send constructor
CGCMsg( MsgType_t eMsg, uint32 cubReserve = 64 ) :
CGCMsgBase( sizeof( MSG_BODY_TYPE ), cubReserve )
{
// Fill out the message header
Hdr().m_eMsg = eMsg;
Hdr().m_nHdrVersion = k_nHdrVersion;
Hdr().m_JobIDSource = k_GIDNil;
Hdr().m_JobIDTarget = k_GIDNil;
}
// reply constructor
CGCMsg( MsgType_t eMsg, const CGCMsgBase &msg, uint32 cubReserve = 64 ) :
CGCMsgBase( sizeof( MSG_BODY_TYPE ), cubReserve )
{
// Fill out the message header
Hdr().m_eMsg = eMsg;
Hdr().m_ulSteamID = msg.Hdr().m_ulSteamID;
Hdr().m_nHdrVersion = k_nHdrVersion;
Hdr().m_JobIDSource = k_GIDNil;
Hdr().m_JobIDTarget = msg.Hdr().m_JobIDSource;
}
CGCMsg( uint8 *pubPkt, uint32 cubPkt ) :
CGCMsgBase( sizeof( GCMsgHdrEx_t ), sizeof( MSG_BODY_TYPE ), pubPkt, cubPkt )
{
}
// Receive constructor
// Use this constructor when creating a message from a received network packet
CGCMsg( CIMsgNetPacketAutoRelease &refNetPacket )
:CGCMsgBase( sizeof( GCMsgHdrEx_t ), sizeof( MSG_BODY_TYPE ), refNetPacket->PubData(), refNetPacket->CubData() )
{
}
// Receive constructor
// Use this constructor when creating a message from a received network packet
CGCMsg( IMsgNetPacket *pNetPacket )
:CGCMsgBase( sizeof( GCMsgHdrEx_t ), sizeof( MSG_BODY_TYPE ), pNetPacket->PubData(), pNetPacket->CubData() )
{
}
// Receive constructor
// Use this constructor when creating a message from a received network packet
CGCMsg( CNetPacket *pNetPacket )
:CGCMsgBase( sizeof( GCMsgHdrEx_t ), sizeof( MSG_BODY_TYPE ), pNetPacket->PubData(), pNetPacket->CubData() )
{
}
// empty constructor
CGCMsg() :
CGCMsgBase( sizeof( GCMsgHdrEx_t ), sizeof( MSG_BODY_TYPE ), NULL, 0 )
{
}
~CGCMsg()
{
}
// Accessors
MSG_BODY_TYPE &Body() { return * ( MSG_BODY_TYPE * ) ( m_pubPkt + m_cubMsgHdr ); }
const MSG_BODY_TYPE &Body() const { return * ( MSG_BODY_TYPE * ) ( m_pubPkt + m_cubMsgHdr ); }
GCMsgHdrEx_t * PGCMsgHdr() { return ( ( GCMsgHdrEx_t * ) m_pubPkt ); }
MsgType_t GetEMsg() const { return Hdr().m_eMsg; }
// Called to set the JobID that will be expecting
// a reply to this message.
void ExpectingReply( JobID_t jobIDSource )
{
Hdr().m_JobIDSource = jobIDSource;
}
bool BIsExpectingReply() { return Hdr().m_JobIDSource != k_GIDNil; }
};
} // namespace GCSDK
#endif // GCMSG_H
+42
View File
@@ -0,0 +1,42 @@
//====== Copyright (c), Valve Corporation, All rights reserved. =======
//
// Purpose: Implements singleton datacache with support for yielding updates
//
//=============================================================================
#ifndef GCPARALLELJOBFARM_H
#define GCPARALLELJOBFARM_H
#ifdef _WIN32
#pragma once
#endif
namespace GCSDK
{
class IYieldingParallelFarmJobHandler
{
//
// Derived class instances implementing farm job workload processing must
// be allocated on the heap as the calling job will yield for processing.
//
protected:
//
// BYieldingRunWorkload is called on different newly created farm jobs
// Param passed iJobSequenceCounter starts at 0 (zero) for the first framed job
// and is incremented by one for each subsequently farmed job.
// When the parallel processing should end job must set pbWorkloadCompleted to true
// and return true as success.
// Any farmed job returning false will abort further farmed processing and
// will result in BYieldingExecuteParallel returning false to the caller job.
//
virtual bool BYieldingRunWorkload( int iJobSequenceCounter, bool *pbWorkloadCompleted ) = 0;
public:
bool BYieldingExecuteParallel( int numJobsParallel, char const *pchJobName = NULL, uint nTimeoutSec = 0 );
};
} // namespace GCSDK
#endif // GCPARALLELJOBFARM_H
+129
View File
@@ -0,0 +1,129 @@
//========= Copyright (c), Valve LLC, All rights reserved. ============
//
// Purpose: A utility for printing out reports on the GC in an ordered manner
//
//=============================================================================
#ifndef GCREPORTPRINTER_H
#define GCREPORTPRINTER_H
#pragma once
#include "gclogger.h"
namespace GCSDK
{
//-----------------------------------------------------------------------------------------------------
// CGCReportPrinter - A class that will handle formatting a report for outputting to a console or other
// data sources. Just create an instance of the class, add the columns, then add the fields, and print. Below is an example:
//
// CGCReportPrinter rp;
// rp.AddStringColumn( "Names" );
// rp.AddFloatColumn( "SomeValue", 2, CGCReportPrinter::eSummary_Total );
// FOR_EACH_VALUE( v )
// rp.StrValue( v.Name );
// rp.FloatValue( v.Float );
// rp.CommitRow();
// rp.SortReport( "SomeValue" );
// rp.PrintReport( SPEW_CONSOLE );
//
//-----------------------------------------------------------------------------------------------------
class CGCReportPrinter
{
public:
CGCReportPrinter();
~CGCReportPrinter();
//what type of summary to report at the end of the report for the column (not used for string columns)
enum ESummaryType
{
eSummary_None,
eSummary_Total,
eSummary_Max
};
//how to format memory values
enum EIntDisplayType
{
eIntDisplay_Normal,
eIntDisplay_Memory_MB,
};
//called to handle inserting columns into the report of various data types. These must be called before any data has been added
//to the report, and will fail if there is outstanding data
bool AddStringColumn( const char* pszColumn );
bool AddSteamIDColumn( const char* pszColumn );
bool AddIntColumn( const char* pszColumn, ESummaryType eSummary, EIntDisplayType eIntDisplay = eIntDisplay_Normal );
bool AddFloatColumn( const char* pszColumn, ESummaryType eSummary, uint32 unNumDecimal = 2 );
//called to reset all report data
void ClearData();
//called to reset the entire report
void Clear();
//called to add the various data to the report, the order of this must match the columns that were added originally
bool StrValue( const char* pszStr, const char* pszLink = NULL );
bool IntValue( int64 nValue, const char* pszLink = NULL );
bool FloatValue( double fValue, const char* pszLink = NULL );
bool SteamIDValue( CSteamID id, const char* pszLink = NULL );
//called to commit the values that have been added as a new row
bool CommitRow();
//sorts the report based upon the specified column name
void SortReport( const char* pszColumn, bool bDescending = true );
//same as the above, but sorts based upon the specified column index
void SortReport( uint32 nColIndex, bool bDescending = true );
//called to print out the provided report
void PrintReport( CGCEmitGroup& eg, uint32 nTop = 0 );
private:
friend class CReportRowSorter;
//the type of each column
enum EColumnType
{
eCol_String,
eCol_Int,
eCol_Float,
eCol_SteamID,
};
//our list of columns
struct Column_t
{
CUtlString m_sName;
EColumnType m_eType;
ESummaryType m_eSummary;
uint8 m_nNumDecimals; //for floats only
EIntDisplayType m_eIntDisplay; // for ints only
};
CUtlVector< Column_t > m_Columns;
//a variant that holds onto the column field data
struct Variant_t
{
Variant_t();
CUtlString m_sStr;
CUtlString m_sLink; //optional link to put around the value
int64 m_nInt;
double m_fFloat;
CSteamID m_SteamID;
};
//our data block
typedef CCopyableUtlVector< Variant_t > TRow;
CUtlVector< TRow* > m_Rows;
//a row that isn't quite in the table, but consists of the row being built to
//avoid issues with partial rows
TRow m_RowBuilder;
};
} // namespace GCSDK
#endif // GCLOGGER_H
+50
View File
@@ -0,0 +1,50 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef GC_SCHEMA_H
#define GC_SCHEMA_H
#ifdef _WIN32
#pragma once
#endif
#include "tier0/platform.h"
#include "steam/steamtypes.h"
#include "tier0/dbg.h"
// include this before checksum_crc specifically to avoid the
// CRC references
#include "steam/steamclientpublic.h"
#include "tier1/utlmap.h"
#include "tier1/utlstring.h"
#include "tier1/utlbuffer.h"
#include "tier1/mempool.h"
#include "tier1/tsmempool.h"
#include "tier1/tsmultimempool.h"
#include "tier1/fmtstr.h"
#include "vstdlib/coroutine.h"
// public stuff
#include "gamecoordinator/igcsqlresultsetlist.h"
// These are first since they're used all over
#include "gcconstants.h"
#include "refcount.h"
// SQL Access stuff
#include "sqlaccess/record.h"
#include "sqlaccess/schema.h"
#include "sqlaccess/recordinfo.h"
#include "sqlaccess/schemafull.h"
#endif
+105
View File
@@ -0,0 +1,105 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: includes all the headers required for the GC SDK. Include this
// in your stdafx.h
//
//=============================================================================
#ifndef GCSDK_H
#define GCSDK_H
#ifdef _WIN32
#pragma once
#endif
#if defined(_WIN32) || defined(_WIN64)
#pragma once
#include <intrin.h>
#pragma intrinsic(_BitScanReverse)
#endif
#include "tier0/platform.h"
#include "gcsteamdefines.h"
#include "steam/steamtypes.h"
#include "tier0/dbg.h"
#ifdef DBGFLAG_VALIDATE
#include "tier0/validator.h"
#endif
#include "tier0/vprof.h"
#include "tier0/fasttimer.h"
// include this before checksum_crc specifically to avoid the
// CRC references
#include "steam/steamclientpublic.h"
#include "tier1/utlmap.h"
#include "tier1/utllinkedlist.h"
#include "tier1/utlpriorityqueue.h"
#include "tier1/utlstring.h"
#include "tier1/utlbuffer.h"
#include "tier1/utldict.h"
#include "tier1/utlhashmaplarge.h"
#include "tier1/mempool.h"
#include "tier1/tsmempool.h"
#include "tier1/tsmultimempool.h"
#include "tier1/checksum_crc.h"
#include "tier1/fmtstr.h"
#include "tier1/KeyValues.h"
#include "tier1/strtools.h"
#include "tier1/utlsymbol.h"
#include "tier1/utlsymbollarge.h"
#include "vstdlib/coroutine.h"
#include "vstdlib/osversion.h"
// public stuff
#include "gamecoordinator/igcsqlresultsetlist.h"
#include "misc.h"
// These are first since they're used all over
#include "gcconstants.h"
#include "refcount.h"
#include "netpacket.h"
#include "gcmsg.h"
#include "msgprotobuf.h"
#include "gc_convar.h"
// SQL Access stuff
#include "sqlaccess/record.h"
#include "sqlaccess/schema.h"
#include "sqlaccess/recordinfo.h"
#include "sqlaccess/schemafull.h"
#include "sqlaccess/columnset.h"
#include "sqlaccess/sqlrecord.h"
#include "sqlaccess/sqlutil.h"
#include "sqlaccess/sqlaccess.h"
#include "messagelist.h"
#include "gchost.h"
#include "gclogger.h"
#include "gcsqlquery.h"
#include "jobtime.h"
#include "job.h"
#include "jobmgr.h"
#include "netpacketpool.h"
#include "gcsystemmsgs.h"
#include "gcwgjobmgr.h"
#include "gcbase.h"
#include "gcsession.h"
#include "sharedobject.h"
#include "protobufsharedobject.h"
#include "schemasharedobject.h"
#include "sharedobjectcache.h"
#include "gcdirtyfield.h"
#include "gc_sharedobjectcache.h"
#include "http.h"
#include "gcwebapi.h"
#include "gcwebapikey.h"
#include "webapi_response.h"
#include "gcjob.h"
#include "msgprotobuf.h"
#include "sdocache.h"
#endif // GCSDK_H
+8
View File
@@ -0,0 +1,8 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// automatically include the right GCSDK header to get
// appropriate stuff included from other GCSDK headers
#ifdef GC
#include "gcsdk.h"
#else
#include "gcclientsdk.h"
#endif
+236
View File
@@ -0,0 +1,236 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Holds the CGCSession class
//
//=============================================================================
#ifndef GCSESSION_H
#define GCSESSION_H
#ifdef _WIN32
#pragma once
#endif
#include "scheduledfunction.h"
#include "framefunction.h"
#include "gcsdk/gc_sharedobjectcache.h"
namespace GCSDK
{
class CGCGSSession;
// Spew group for anything related to sessions
extern CGCEmitGroup g_EGSessions;
//-----------------------------------------------------------------------------
// Utility class to handle rate limiting based upon a steam ID and message using two console variables to control rate
//-----------------------------------------------------------------------------
//utility class to handle rate limiting based upon a steam ID
class CSteamIDRateLimit
{
public:
CSteamIDRateLimit( const GCConVar& cvNumPerPeriod, const GCConVar* pcvPeriodS = NULL );
~CSteamIDRateLimit();
//given a steam ID, this will determine if it should be rate limited
bool BIsRateLimited( CSteamID steamID, uint32 unMsgType );
//frame function to clear the list after a period of time
bool OnFrameFn( const CLimitTimer& timer );
private:
//the last time we cleared our list
RTime32 m_LastClear;
//the frame function so we can detect when we need to clear
CFrameFunction< CSteamIDRateLimit> m_FrameFunction;
//the map of messages we have tracked for each user
CUtlHashMapLarge< CSteamID, uint32 > m_Msgs;
//the console variables that track the time window and the messages allowed
const GCConVar& m_cvNumPerPeriod;
const GCConVar* m_pcvPeriodS;
};
//------------------------------------------------------------------------------------------
// CMsgRateLimitTracker
// A utility class to track when messages go over so that we can see users/msgs that are being spammed
//------------------------------------------------------------------------------------------
class CMsgRateLimitTracker
{
public:
CMsgRateLimitTracker();
//called to track a message that was rate limited
void TrackRateLimitedMsg( const CSteamID steamID, MsgType_t eMsgType );
//called to report the collected rate limiting stats
void ReportMsgStats() const;
void ReportTopUsers( uint32 nMinMsgs, uint32 nListTop ) const;
void ReportUserStats() const;
//called to clear all collected stats
void ClearStats();
private:
//the time we started collecting stats at
RTime32 m_StartTime;
//map detailing the number of messages of each type that have been dropped
CUtlHashMapLarge< MsgType_t, uint32 > m_MsgStats;
CUtlHashMapLarge< CSteamID, uint32 > m_UserStats;
};
extern CMsgRateLimitTracker g_RateLimitTracker;
//-----------------------------------------------------------------------------
// Purpose: Base class for sessions in the GC
//-----------------------------------------------------------------------------
class CGCSession
{
public:
CGCSession( const CSteamID & steamID, CGCSharedObjectCache *pCache );
virtual ~CGCSession();
const CSteamID & GetSteamID() const { return m_steamID; }
const CGCSharedObjectCache *GetSOCache() const { return m_pSOCache; }
CGCSharedObjectCache *GetSOCache() { return m_pSOCache; }
void RemoveSOCache() { m_pSOCache = NULL; }
EOSType GetOSType() const { return m_osType; };
bool IsTestSession() const { return m_bIsTestSession; }
uint32 GetIPPublic() const { return m_unIPPublic; }
bool IsSecure() const { return m_bIsSecure; }
bool BIsShuttingDown() const { return m_bIsShuttingDown; }
void SetIsShuttingDown( bool bIsShuttingDown ) { m_bIsShuttingDown = bIsShuttingDown; }
virtual void Dump( bool bFull = true ) const = 0;
bool BRateLimitMessage( MsgType_t unMsgType );
CJobTime GetLastPingSendTime() const { return m_jtTimeSentPing; }
CJobTime GetLastMessageReceiveTime() const { return m_jtLastMessageReceived; }
void SendPing() const;
virtual void MarkAccess() { }
virtual void Run();
virtual void YieldingSOCacheReloaded() {}
#ifdef DBGFLAG_VALIDATE
virtual void Validate( CValidator &validator, const char *pchName );
#endif // DBGFLAG_VALIDATE
// Geolocation
bool HasGeoLocation() const { return m_haveGeoLocation; }
bool GetGeoLocation( float &latitude, float &longittude ) const;
virtual void SetGeoLocation( float latitude, float longittude );
//track whether or not this session has been initialized or not
bool BIsInitialized() const { return m_bInitialized; }
void SetInitialized( bool b ) { m_bInitialized = b; }
private:
CSteamID m_steamID;
CGCSharedObjectCache *m_pSOCache;
// Tracks how many messages we've gotten this second so we can block attacks
RTime32 m_rtLastMessageReceived;
uint32 m_unMessagesRecievedThisSecond;
CJobTime m_jtLastMessageReceived;
// This is mutable because we update it when we send pings, but sending a
// ping to a user/server isn't really a session changing event, so we don't
// want to require locking the session to ping it and update its last
// sent ping time.
mutable CJobTime m_jtTimeSentPing;
EOSType m_osType : 16;
bool m_bIsShuttingDown : 1;
bool m_bIsTestSession : 1;
bool m_bIsSecure : 1;
bool m_bInitialized : 1;
protected:
bool m_haveGeoLocation : 1;
float m_flLatitude;
float m_flLongitude;
uint32 m_unIPPublic;
friend class CGCBase;
};
//-----------------------------------------------------------------------------
// Purpose: Base class for user sessions in the GC
//-----------------------------------------------------------------------------
class CGCUserSession : public CGCSession
{
public:
CGCUserSession( const CSteamID & steamID, CGCSharedObjectCache *pCache ) : CGCSession( steamID, pCache ) { }
virtual ~CGCUserSession();
virtual bool BInit();
const CSteamID &GetSteamIDGS() const { return m_steamIDGS; }
const CSteamID &GetSteamIDGSPrev() const { return m_steamIDGSPrev; }
virtual bool BSetServer( const CSteamID &steamIDGS );
virtual bool BLeaveServer();
virtual void Dump( bool bFull = true ) const;
private:
CSteamID m_steamIDGS;
CSteamID m_steamIDGSPrev;
};
//-----------------------------------------------------------------------------
// Purpose: Base class for gameserver sessions in the GC
//-----------------------------------------------------------------------------
class CGCGSSession : public CGCSession
{
public:
CGCGSSession( const CSteamID & steamID, CGCSharedObjectCache *pCache, uint32 unServerAddr, uint16 usServerPort ) ;
virtual ~CGCGSSession();
uint32 GetAddr() const { return m_unServerAddr; }
uint16 GetPort() const { return m_usServerPort; }
void SetIPAndPort( uint32 unServerAddr, uint16 usServerPort );
int GetUserCount() const { return m_vecUsers.Count(); }
CSteamID GetUserID( int nIndex ) const { return m_vecUsers[nIndex]; }
// Manages users on the server. It is very important that these are not
// virtual and not yielding. For custom behavior override the Pre*() hooks below
bool BAddUser( const CSteamID &steamIDUser );
bool BRemoveUser( const CSteamID &steamIDUser );
void RemoveAllUsers();
virtual void Dump( bool bFull = true ) const;
protected:
// Hooks to trigger custom behavior when users are added and removed. It is
// very important that these do not yield. If you need to yield, start a job instead
virtual void PreAddUser( const CSteamID &steamIDUser ) {}
virtual void PostAddUser( const CSteamID &steamIDUser ) {}
virtual void PreRemoveUser( const CSteamID &steamIDUser ) {}
virtual void PostRemoveUser( const CSteamID &steamIDUser ) {}
virtual void PreRemoveAllUsers() {}
virtual void PostRemoveAllUsers() {}
public:
float m_lastUpdateTime; // Last time we received a message from the server
#ifdef DBGFLAG_VALIDATE
virtual void Validate( CValidator &validator, const char *pchName );
#endif // DBGFLAG_VALIDATE
protected:
CUtlVector<CSteamID> m_vecUsers;
// These are the address of the server as connected to Steam
uint32 m_unServerAddr;
uint16 m_usServerPort;
};
} // namespace GCSDK
#endif // GCSESSION_H
+163
View File
@@ -0,0 +1,163 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================
#ifndef GCSQLQUERY_H
#define GCSQLQUERY_H
#ifdef _WIN32
#pragma once
#endif
#include "gamecoordinator/igcsqlquery.h"
#include "refcount.h"
#include "bufferpool.h"
namespace GCSDK
{
struct GCSQLBindParam_t
{
EGCSQLType m_eType;
size_t m_nOffset;
size_t m_cubData;
};
class CGCSQLQuery
{
DECLARE_CLASS_MEMPOOL( CGCSQLQuery );
public:
CGCSQLQuery();
virtual ~CGCSQLQuery();
static CBufferPool &GetBufferPool();
void SetCommand( const char *pchCommand ) { m_sCommand = pchCommand; }
void AddBindParam( const char *pchValue )
{
AddBindParamRaw( k_EGCSQLType_String, (byte *)pchValue, Q_strlen( pchValue ) );
}
void AddBindParam( const int16 nValue )
{
AddBindParamRaw( k_EGCSQLType_int16, (byte *)&nValue, sizeof( nValue ) );
}
void AddBindParam( const uint16 uValue )
{
AddBindParamRaw( k_EGCSQLType_int16, (byte *)&uValue, sizeof( uValue ) );
}
void AddBindParam( const int32 nValue )
{
AddBindParamRaw( k_EGCSQLType_int32, (byte *)&nValue, sizeof( nValue ) );
}
void AddBindParam( const uint32 uValue )
{
AddBindParamRaw( k_EGCSQLType_int32, (byte *)&uValue, sizeof( uValue ) );
}
void AddBindParam( const uint64 ulValue )
{
AddBindParamRaw( k_EGCSQLType_int64, (byte *)&ulValue, sizeof( ulValue ) );
}
void AddBindParam( const uint8 *ubValue, const int cubValue )
{
AddBindParamRaw( k_EGCSQLType_Blob, (byte *)ubValue, cubValue );
}
void AddBindParam( const float fValue )
{
AddBindParamRaw( k_EGCSQLType_float, (byte *)&fValue, sizeof ( fValue ) );
}
void AddBindParam( const double dValue )
{
AddBindParamRaw( k_EGCSQLType_double, (byte *)&dValue, sizeof ( dValue ) );
}
// Image needs a special type since the default var data is blob
void AddBindParamImage( const uint8 *ubValue, const int cubValue )
{
AddBindParamRaw( k_EGCSQLType_Image, (byte *)ubValue, cubValue );
}
void ClearParams();
// this is used internally to bind a field with its type. You probably want
// some version of AddBindParam instead of this.
void AddBindParamRaw( EGCSQLType eType, const byte *pubData, uint32 cubData );
// ------- Interface implementation from IGCSQLQuery -----
// get the null-terminated query string itself
virtual const char *PchCommand() { return m_sCommand.Get(); }
// gets the parameter data
virtual uint32 CnParams() { return m_vecParams.Count(); }
virtual EGCSQLType EParamType( uint32 uIndex ) { return m_vecParams[uIndex].m_eType; }
virtual byte *PubParam( uint32 uIndex ) { return (byte *)m_pBufParams->Base() + m_vecParams[uIndex].m_nOffset; }
virtual uint32 CubParam( uint32 uIndex ) { return ( uint32 )m_vecParams[uIndex].m_cubData; }
private:
CUtlString m_sCommand;
CUtlBuffer *m_pBufParams;
CUtlVectorFixedGrowable< GCSQLBindParam_t, 10 > m_vecParams;
};
class CGCSQLQueryGroup : public IGCSQLQuery, public CRefCount
{
DECLARE_CLASS_MEMPOOL( CGCSQLQuery );
private:
// create query groups on the heap with alloc
CGCSQLQueryGroup();
// destroy query groups by releasing them
virtual ~CGCSQLQueryGroup();
public:
static CGCSQLQueryGroup *Alloc() { return new CGCSQLQueryGroup(); }
void AddQuery( CGCSQLQuery *pQuery );
void SetName( const char *sName );
// returns the number of statements in the transaction
// represented by this query object
virtual uint32 GetStatementCount() { return m_vecQueries.Count(); }
// returns a string that represents where in the GC this
// query came from. Usually this is FILE_AND_LINE.
virtual const char *PchName() { return m_sName; }
// get the null-terminated query string itself
virtual const char *PchCommand( uint32 unStatement ) { return m_vecQueries[unStatement]->PchCommand(); }
// gets the parameter data
virtual uint32 CnParams( uint32 unStatement ) { return m_vecQueries[unStatement]->CnParams(); }
virtual EGCSQLType EParamType( uint32 unStatement, uint32 uIndex ) { return m_vecQueries[unStatement]->EParamType( uIndex ); }
virtual byte *PubParam( uint32 unStatement, uint32 uIndex ) { return m_vecQueries[unStatement]->PubParam( uIndex ); }
virtual uint32 CubParam( uint32 unStatement, uint32 uIndex ) { return m_vecQueries[unStatement]->CubParam( uIndex ); };
// reports the result
virtual void SetResults( IGCSQLResultSetList *pResults );
IGCSQLResultSetList *GetResults() { return m_pResults; }
// clears all the queries in the query group and resets its name
void Clear();
private:
CUtlVector< CGCSQLQuery * > m_vecQueries;
CUtlString m_sName;
IGCSQLResultSetList *m_pResults;
};
} // namespace GCSDK
#include "tier0/memdbgoff.h"
#endif // GCSQLQUERY_H
+131
View File
@@ -0,0 +1,131 @@
//====== Copyright ©, Valve Corporation, All rights reserved. =======
// GCSqlWriteQueue.h
//
// A utility class that allows for templating based upon a SQL schema type, and then
// queuing up a number of those records to be written to SQL. This will buffer them until
// either a certain number have been queued or a period of time has elapsed, at which point
// it will flush them to SQL in a single transaction.
//
//===================================================================
#ifndef GCSQLWRITEQUEUE_H
#define GCSQLWRITEQUEUE_H
#include "scheduledfunction.h"
namespace GCSDK
{
class CGCBase;
template < typename TSqlClass >
class CGCSQLWriteQueue
{
public:
CGCSQLWriteQueue( uint32 nMaxToCache, uint32 nMaxMSToWrite ) :
m_nMaxToCache( nMaxToCache ),
m_nMaxMSToWrite( nMaxMSToWrite )
{
m_QueuedRecords.EnsureCapacity( nMaxToCache );
}
//called to queue the record for writing, which will occur either when the maximum time between writebacks has occurred, or
//when the cache has filled up
void QueueRecord( const TSqlClass& sch )
{
m_QueuedRecords.AddToTail( sch );
//now handle either dispatching, or scheduling a timeout dispatch
if( ( uint32 )m_QueuedRecords.Count() >= m_nMaxToCache )
{
//unschedule first. This way if while we are yielded, we add another entry, it can schedule a new timeout
m_TimeCommit.Cancel();
CreateJobToCommitSQL();
}
else if( !m_TimeCommit.BIsScheduled() )
{
m_TimeCommit.Schedule( this, &CGCSQLWriteQueue< TSqlClass >::CreateJobToCommitSQL, m_nMaxMSToWrite );
}
}
//a yielding version of the above function
void YieldingQueueRecord( const TSqlClass& sch )
{
m_QueuedRecords.AddToTail( sch );
//now handle either dispatching, or scheduling a timeout dispatch
if( ( uint32 )m_QueuedRecords.Count() >= m_nMaxToCache )
{
//unschedule first. This way if while we are yielded, we add another entry, it can schedule a new timeout
m_TimeCommit.Cancel();
YieldingFlushQueuedViewsToSQL();
}
else if( !m_TimeCommit.BIsScheduled() )
{
m_TimeCommit.Schedule( this, &CGCSQLWriteQueue< TSqlClass >::CreateJobToCommitSQL, m_nMaxMSToWrite );
}
}
private:
//called internally when we kick off a job after N amount of time has expired
template < typename TSqlClass >
class CTimeExpiredCommitJob : public CGCJob
{
public:
CTimeExpiredCommitJob( CGCBase* pGC, CGCSQLWriteQueue< TSqlClass >* pClass ) : CGCJob( pGC ), m_pClass( pClass ) {}
virtual bool BYieldingRunJob( void* pvStartParm )
{
m_pClass->YieldingFlushQueuedViewsToSQL();
return true;
}
private:
CGCSQLWriteQueue< TSqlClass >* m_pClass;
};
//the function called when time expires to start a job and commit the requests to SQL
void CreateJobToCommitSQL()
{
//kick off our job, which just calls the flush
CGCJob* pJob = new CTimeExpiredCommitJob< TSqlClass >( GGCBase(), this );
pJob->StartJobDelayed( NULL );
}
//handles committing the list of queued views to SQL
void YieldingFlushQueuedViewsToSQL()
{
if( m_QueuedRecords.Count() == 0 )
return;
//move the contents into a local vector so we don't have any conflicts of global state
CUtlVector< TSqlClass > localQueue;
localQueue.Swap( m_QueuedRecords );
//prepare the queue for the next batch (so we don't have intermediate resizes)
m_QueuedRecords.EnsureCapacity( m_nMaxToCache );
// start a transaction for all this work
CSQLAccess sqlAccess;
sqlAccess.BBeginTransaction( "CGCWatchDownloadedReplayJob::FlushQueuedViewsToSQL" );
FOR_EACH_VEC( localQueue, nCurrView )
{
sqlAccess.BYieldingInsertRecord( &localQueue[ nCurrView ] );
}
sqlAccess.BCommitTransaction();
}
//the records that we have queued
CUtlVector< TSqlClass > m_QueuedRecords;
//schedules a write back to ensure we commit at least every N seconds
CScheduledFunction< CGCSQLWriteQueue< TSqlClass > > m_TimeCommit;
//maximum number of seconds between commits
uint32 m_nMaxMSToWrite;
//maximum number of records to buffer before writing back
uint32 m_nMaxToCache;
};
} //namespace GCSDK
#endif
+48
View File
@@ -0,0 +1,48 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Defines a bunch of stuff that would be defined in Steam, but
// isn't in Source.
//
//=============================================================================
#ifndef GCSTEAMDEFINES_H
#define GCSTEAMDEFINES_H
#ifdef _WIN32
#pragma once
#endif
#include "tier0/memalloc.h"
// steam defines some things that games don't
#ifndef STEAM
#define PvAlloc(x) malloc(x)
#define PvRealloc(x, y) realloc(x, y)
#define FreePv(x) free(x)
// auto-lock class for read-write locks
template< class T >
class CRWLockAutoWrite
{
T &m_RWLock;
public:
CRWLockAutoWrite( T &RWLock ) : m_RWLock( RWLock )
{
m_RWLock.LockForWrite();
}
~CRWLockAutoWrite()
{
m_RWLock.UnlockWrite();
}
};
#define AUTO_LOCK_WRITE( mutex ) CRWLockAutoWrite<CThreadRWLock> UNIQUE_ID( mutex )
#define AUTO_LOCK_SPIN_WRITE( mutex ) CRWLockAutoWrite<CThreadSpinRWLock> UNIQUE_ID( mutex )
inline void *MemAlloc_AllocAligned( size_t size, size_t align, bool bCanFail ) { return MemAlloc_AllocAligned( size, align ); }
inline void MemAlloc_FreeAligned( void *pMemBlock, bool bOperatorNew ) { MemAlloc_FreeAligned( pMemBlock ); }
#endif
#endif // GCSTEAMDEFINES_H
+255
View File
@@ -0,0 +1,255 @@
//========= Copyright (c), Valve LLC, All rights reserved. ============
//
// Purpose: A utility for tracking access to various systems
//
//=============================================================================
#ifndef GCSYSTEMACCESS_H
#define GCSYSTEMACCESS_H
#pragma once
namespace GCSDK
{
//behaviors that can be triggered for system access violation. Each aspect of this system has
//actions that can be enabled or suppressed. The algorithm for this is to combine all the enabled
//actions, then remove all the suppressed and perform those operations when a violation is found
enum EGCAccessAction
{
//returns failure to the verifying code so it can handle it accordingly
GCAccessAction_ReturnFail = ( 1<<0 ),
//track this into a stats overview for number of offenses
GCAccessAction_TrackFail = ( 1<<1 ),
//track this into a stats overview for number of accesses
GCAccessAction_TrackSuccess = ( 1<<2 ),
//report this violation to the console/log
GCAccessAction_Msg = ( 1<<3 ),
//generate an assert at the point this is violated
GCAccessAction_Assert = ( 1<<4 ),
//activate a breakpoint
GCAccessAction_Breakpoint = ( 1<<5 )
};
//how we identify an actual system
typedef uint32 GCAccessSystem_t;
//a structure that is inserted into a context object and holds onto GCAccessSystem index that the context can then initialize itself from. Classes can derive
//from this to initialize itself, but derived class may NOT have any members as that would change the binary layout of the object
class CGCAContextSystemRef
{
public:
CGCAContextSystemRef( GCAccessSystem_t id ) : m_SystemID( id ) {}
const GCAccessSystem_t m_SystemID;
};
//this will generate a unique system access ID that is guaranteed to be unique within a single run of the GC
GCAccessSystem_t GenerateUniqueGCAccessID();
//defines a system access object class which can be used to validate access to systems. This class can be registered with the
//GCA_REGISTER_SYSTEM macro using the same name.
#define GCA_SYSTEM( Name ) \
class CGCA##Name : public CGCAContextSystemRef { \
public: \
static const char* GetName() { return #Name; } \
static GCAccessSystem_t GetID() { static GCAccessSystem_t s_ID = GenerateUniqueGCAccessID(); return s_ID; } \
CGCA##Name() : CGCAContextSystemRef( GetID() ) {} \
};
//similar to the above, but does not auto assign an ID and instead uses the fixed specified ID
#define GCA_SYSTEM_ID( Name, ID ) \
class CGCA##Name : public CGCAContextSystemRef { \
public: \
static const char* GetName() { return #Name; } \
static GCAccessSystem_t GetID() { return (ID); } \
CGCA##Name() : CGCAContextSystemRef( GetID() ) {} \
};
//called to register the specified system with the GC access system
#define GCA_REGISTER_SYSTEM( Name ) GGCAccess().RegisterSystem( CGCA##Name::GetName(), CGCA##Name::GetID() )
//called to begin a context class which derives from (has the same rights as) a parent context. The proper setup for this is:
// GCA_BEGIN_CONTEXT_PARENT( MyContext, ParentContext )
// GCA_CONTEXT_SYSTEM( System )
// GCA_END_CONTEXT( MyContext )
#define GCA_BEGIN_CONTEXT_PARENT( ClassName, Parent ) \
class ClassName : public Parent { \
public: \
ClassName() : m_nFirstSystemMarker_##ClassName( 0 ), m_nLastSystemMarker_##ClassName( 0 ) { \
Init( #ClassName ); \
for( const GCAccessSystem_t* pSystem = &m_nFirstSystemMarker_##ClassName + 1; pSystem != &m_nLastSystemMarker_##ClassName; pSystem++ ) \
AddSystem( *pSystem ); \
} \
static ClassName& Singleton() { static ClassName s_Singleton; return s_Singleton; } \
const GCAccessSystem_t m_nFirstSystemMarker_##ClassName;
//this is the same as the above but where you don't specify a parent context
#define GCA_BEGIN_CONTEXT( ClassName ) GCA_BEGIN_CONTEXT_PARENT( ClassName, CGCAccessContext )
//called to add a context to a system. This must come between a BEGIN/END_CONTEXT block
#define GCA_CONTEXT_SYSTEM( SystemName ) \
const CGCA##SystemName SystemName;
//called to add a generic system reference to a context. The reference will use the provided class and value name and the specified ID
#define GCA_CONTEXT_SYSTEM_ID( ClassName, VarName, SystemID ) \
class ClassName : public CGCAContextSystemRef { \
public: \
ClassName() : CGCAContextSystemRef( SystemID ) {} \
} VarName;
//closes out the definition of a context
#define GCA_END_CONTEXT( ClassName ) \
const GCAccessSystem_t m_nLastSystemMarker_##ClassName; \
};
//creates a context that is basically the same as another context but aliased. Useful if you want a derived context that doesn't have any additional systems
#define GCA_ALIAS_CONTEXT( NewName, ParentName ) \
GCA_BEGIN_CONTEXT_PARENT( NewName, ParentName ) \
GCA_END_CONTEXT( NewName )
//a simple way to define an empty context
#define GCA_EMPTY_CONTEXT( Context ) GCA_BEGIN_CONTEXT( Context ) GCA_END_CONTEXT( Context )
//called to obtain the ID of a system
#define GCA_GET_SYSTEM_ID( SystemName ) ( CGCA##SystemName::GetID() )
//called to validate access to a system, either with an ID or without
#define GCA_VALIDATE_ACCESS( SystemName ) GGCAccess().ValidateAccess( GCA_GET_SYSTEM_ID( SystemName ) )
#define GCA_VALIDATE_ACCESS_STEAMID( SystemName, SteamID ) GGCAccess().ValidateSteamIDAccess( GCA_GET_SYSTEM_ID( SystemName ), ( SteamID ) )
//a context, which is a collection of systems that are valid to be accessed. Each job and GC has a context to validate
//system access
class CGCAccessContext
{
public:
CGCAccessContext();
void Init( const char* pszName, uint32 nActions = 0, uint32 nSuppressActions = 0 );
void AddSystem( GCAccessSystem_t nSystem );
const char* GetName() const { return m_sName.String(); }
uint32 GetActions() const { return m_nActions; }
uint32 GetSuppressActions() const { return m_nSuppressActions; }
void SetAction( EGCAccessAction eAction, bool bSet );
void SetSuppressAction( EGCAccessAction eAction, bool bSet );
//determines if this system has the
bool HasSystem( GCAccessSystem_t system ) const;
//given a context, this will verify that it is a proper subset of the provided context
bool IsSubsetOf( const CGCAccessContext& context ) const;
//given another context, this will add all of its systems to this context
void AddSystemsFrom( const CGCAccessContext& context );
private:
//the textual name of this context
CUtlString m_sName;
//which actions to enable or disable in particular based upon this context
uint32 m_nActions;
uint32 m_nSuppressActions;
//which systems that the context has enabled, in sorted order for fast lookup
CUtlSortVector< GCAccessSystem_t > m_nSystems;
};
class CGCAccessSystem;
//the global GC access tracker which has the systems registered and handles accumulating stats, presenting reports, etc
class CGCAccess
{
public:
CGCAccess();
//-----------------------------------------
// Setup
//called to register a system information
void RegisterSystem( const char* pszName, GCAccessSystem_t nID, uint32 nActions = 0, uint32 nSupressActions = 0 );
//-----------------------------------------
// Validation
//called to verify access to a system. The return of this will be false if it is an invalid access and the return fail action is specified
bool ValidateAccess( GCAccessSystem_t nSystem );
//verify access to a system that also requires that the active SteamID of the current job matches the provided Steam ID, and count it as a fail
bool ValidateSteamIDAccess( GCAccessSystem_t nSystem, CSteamID steamID );
//called to suppress tracking of access for the specified access type. This should typically only be accessed via the access supress utility object
void SuppressAccess( GCAccessSystem_t nSystem, bool bEnable );
//-----------------------------------------
// Report and console operations
//when displaying a report, this will determine how stats should be filtered
enum EDisplay
{
eDisplay_Referenced,
eDisplay_Violations,
eDisplay_IDViolations,
eDisplay_All
};
//called to reset all accumulated stats for all the systems
void ClearSystemStats();
//called to generate a report of every access for a specific job
void ReportJobs( const char* pszContext, EDisplay eDisplay ) const;
//called to generate a report of each system in summary
void ReportSystems( const char* pszContext, EDisplay eDisplay ) const;
//dumps all the collected stats
void FullReport( const char* pszSystemFilter, const char* pszContextFilter, const char* pszJobFilter, EDisplay eDisplay ) const;
//dumps a dependency report for a given system. Essentially for every job that depends upon the named system, what are the other
//systems that it also relies upon
void DependencyReport( const char* pszSystem, EDisplay eDisplay ) const;
//called to register a one time assert that will fire the next time the job/context/system pair is hit
bool CatchSingleAssert( const char* pszSystem, bool bContext, const char* pszContextOrJob );
//clears all registered single asserts that have not yet fired
void ClearSingleAsserts();
//if there is not a specific job in flight, this global context is what will be checked for access
CGCAccessContext m_GlobalContext;
//global options that are set for typical system violation
uint32 m_nActions;
uint32 m_nSuppressActions;
private:
//handles internally validating the system. Takes an optional parameter indicating if the steam ID check failed
bool InternalValidateAccess( GCAccessSystem_t nSystem, CSteamID steamID, CSteamID expectedID );
//the list of single fire asserts that we want to catch and report
struct SingleAssert_t
{
bool m_bContext;
GCAccessSystem_t m_System;
CUtlString m_sContextOrJob;
};
CUtlVector< SingleAssert_t* > m_SingleAsserts;
//systems that have their access suppressed
struct SuppressAccess_t
{
GCAccessSystem_t m_nSystem;
uint32 m_nCount;
};
CUtlVector< SuppressAccess_t > m_SuppressAccess;
//the registered systems
CUtlHashMapLarge< GCAccessSystem_t, CGCAccessSystem* > m_Systems;
//steam ID
};
//global singleton accessor
CGCAccess& GGCAccess();
//utility class to temporarily disable access tracking for a system while within the scope of this object
class CGCAccessSupressTracking
{
public:
CGCAccessSupressTracking( GCAccessSystem_t nSystem ) : m_nSystem( nSystem ) { GGCAccess().SuppressAccess( m_nSystem, false ); }
~CGCAccessSupressTracking() { GGCAccess().SuppressAccess( m_nSystem, true ); }
private:
GCAccessSystem_t m_nSystem;
};
} //namespace GCSDK
#endif
+216
View File
@@ -0,0 +1,216 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: This file defines all of our over-the-wire net protocols for the
// global system messages used by the GC. These are usually sent by
// the GC Host so be very careful of versioning issues when you consider
// changing them. Note that we never use types with undefined length
// (like int). Always use an explicit type (like int32).
//
//=============================================================================
#ifndef GCSYSTEMMSGS_H
#define GCSYSTEMMSGS_H
#ifdef _WIN32
#pragma once
#endif
// Protobuf headers interfere with the valve min/max/malloc overrides. so we need to do all
// this funky wrapping to make the include happy.
#include <tier0/valve_minmax_off.h>
#include "gcsystemmsgs.pb.h"
#include <tier0/valve_minmax_on.h>
namespace GCSDK
{
#pragma pack( push, 8 ) // this is a 8 instead of a 1 to maintain backward compatibility with Steam
// generic zero-length message struct
struct MsgGCEmpty_t
{
};
// k_EGCMsgAchievementAwarded
struct MsgGCAchievementAwarded_t
{
uint16 m_usStatID;
uint8 m_ubBit;
// var data:
// string data: name of achievement earned
};
// k_EGCMsgConCommand
struct MsgGCConCommand_t
{
// var data:
// string: the command as typed into the console
};
// k_EGCMsgStartPlaying
struct MsgGCStartPlaying_t
{
CSteamID m_steamID;
CSteamID m_steamIDGS;
uint32 m_unServerAddr;
uint16 m_usServerPort;
};
// k_EGCMsgStartPlaying
// k_EGCMsgStopGameserver
struct MsgGCStopSession_t
{
CSteamID m_steamID;
};
// k_EGCMsgStartGameserver
struct MsgGCStartGameserver_t
{
CSteamID m_steamID;
uint32 m_unServerAddr;
uint16 m_usServerPort;
};
// k_EGCMsgWGRequest
struct MsgGCWGRequest_t
{
uint64 m_ulSteamID; //SteamID of auth'd WG user
uint32 m_unPrivilege; // The EGCWebApiPrivilege value that the request was made with
uint32 m_cubKeyValues; // length of the key values data blob in message (starts after string request name data)
// var data -
// request name
// binary key values of web request
};
// k_EGCMsgWGResponse
struct MsgGCWGResponse_t
{
bool m_bResult; // True if the request was successful
uint32 m_cubKeyValues; // length of the key values data blob in message
// var data -
// binary key values of web response
};
// k_EGCMsgGetUserGameStatsSchemaResponse
struct MsgGetUserGameStatsSchemaResponse_t
{
bool m_bSuccess; // True is the request was successful
// var data -
// binary key values containing the User Game Stats schema
};
// k_EGCMsgGetUserStats
struct MsgGetUserStats_t
{
uint64 m_ulSteamID; // SteamID the stats are requested for
uint16 m_cStatIDs; // A count of the number of statIDs requested
// var data -
// Array of m_cStatIDs 16-bit StatIDs
};
// k_EGCMsgGetUserStatsResponse
struct MsgGetUserStatsResponse_t
{
uint64 m_ulSteamID; // SteamID the stats were requested for
bool m_bSuccess; // True is the request was successful
uint16 m_cStats; // Number of stats returned in the message
// var data -
// m_cStats instances of:
// uint16 usStatID - Stat ID
// uint32 unData - Stat value
};
// k_EGCMsgValidateSession
struct MsgGCValidateSession_t
{
uint64 m_ulSteamID; // SteamID to validate
};
// k_EGCMsgValidateSessionResponse
struct MsgGCValidateSessionResponse_t
{
uint64 m_ulSteamID;
uint64 m_ulSteamIDGS;
uint32 m_unServerAddr;
uint16 m_usServerPort;
bool m_bOnline;
};
// response to k_EGCMsgLookupAccountFromInput
struct MsgGCLookupAccountResponse
{
uint64 m_ulSteamID;
};
// k_EGCMsgSendHTTPRequest
struct MsgGCSendHTTPRequest_t
{
// Variable data:
// - Serialized CHTTPRequest
};
// k_EGCMsgSendHTTPRequestResponse
struct MsgGCSendHTTPRequestResponse_t
{
bool m_bCompleted;
// Variable data:
// - if m_bCompleted is true, Serialized CHTTPResponse
};
// k_EGCMsgRecordSupportAction
struct MsgGCRecordSupportAction_t
{
uint32 m_unAccountID; // which account is affected (object)
uint32 m_unActorID; // who made the change (subject)
// Variable data:
// - string - Custom data for the event
// - string - A note with the reason for the change
};
// k_EGCMsgWebAPIRegisterInterfaces
struct MsgGCWebAPIRegisterInterfaces_t
{
uint32 m_cInterfaces;
// Variable data:
// - KeyValues for interface - one per interface
};
// k_EGCMsgGetAccountDetails
struct MsgGCGetAccountDetails_t
{
uint64 m_ulSteamID; // SteamID to validate
};
// Used by k_EGCMsgFindAccounts
enum EAccountFindType
{
k_EFindAccountTypeInvalid = 0,
k_EFindAccountTypeAccountName = 1,
k_EFindAccountTypeEmail,
k_EFindAccountTypePersonaName,
k_EFindAccountTypeURL,
k_EFindAccountTypeAllOnline,
k_EFindAccountTypeAll,
k_EFindClanTypeClanName,
k_EFindClanTypeURL,
k_EFindClanTypeOfficialURL,
k_EFindClanTypeAppID,
};
} // namespace GCSDK
#pragma pack( pop )
#endif // GCSYSTEMMSGS_H
+103
View File
@@ -0,0 +1,103 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: CWAPI header for GC access to the Web API server
//
//=============================================================================
#ifndef GCWEBAPI_H
#define GCWEBAPI_H
#ifdef _WIN32
#pragma once
#endif
#include "tier0/memdbgon.h"
enum EWebAPIPrivilege
{
k_EWebApiPriv_Invalid = -1,
k_EWebApiPriv_None = 0, // fully public, no auth needed
k_EWebApiPriv_Key = 1, // Requires valid key
k_EWebApiPriv_PublisherKey = 2, // Requires publisher key
k_EWebApiPriv_PublisherKeyOwnsApp = 3, // Requires publisher key and publisher owns appid
//k_EWebApiPriv_Account = 1, // user must have a Steam account with password set
};
enum EWebApiParamType
{
k_EWebApiParamTypeInvalid = -1,
k_EWebApiParamTypeInt32 = 0,
k_EWebApiParamTypeUInt32 = 1,
k_EWebApiParamTypeInt64 = 2,
k_EWebApiParamTypeUInt64 = 3,
k_EWebApiParamTypeFloat = 4,
k_EWebApiParamTypeString = 5,
k_EWebApiParamTypeBool = 6,
k_EWebApiParamTypeRawBinary = 7,
};
const char *PchNameFromEWebApiParamType( int eWebApiParamType );
typedef KeyValues *(*GCWebAPIInterfaceMapCreationFunc_t)();
class CGCWebAPIInterfaceMapRegistrar
{
public:
CGCWebAPIInterfaceMapRegistrar( GCWebAPIInterfaceMapCreationFunc_t pFunc )
{
VecInstance().AddToTail( pFunc );
}
static CUtlVector< GCWebAPIInterfaceMapCreationFunc_t > & VecInstance();
};
//
// Macros for use registering interfaces in webapi_interfacemap.h
//
#define BEGIN_GCWEB_INTERFACE_BLOCK( pchInterfaceName ) \
KeyValues *CreateWebAPIInterfaceMap_##pchInterfaceName() \
{ \
KeyValues *pkvInterface = new KeyValues( #pchInterfaceName );
#define DECLARE_GCWEBAPI_METHOD( pchMethodName, unVersion, eHTTPMethod, pchJobName, ePriv ) \
{ \
KeyValues *pkvMethod = pkvInterface->FindKey( pchMethodName #unVersion, true ); \
pkvMethod->SetString( "name", pchMethodName ); \
pkvMethod->SetInt( "version", unVersion ); \
pkvMethod->SetInt( "http_method", eHTTPMethod ); \
pkvMethod->SetString( "job_name", pchJobName); \
pkvMethod->SetInt( "priv", ePriv );
#define REQUIRED_GCWEBAPI_PARAM( pchName, eType, pchDescription ) \
{ \
KeyValues *pkvParams = pkvMethod->FindKey( "params", true ); \
AssertMsg( Q_stricmp( pchName, "format" ) != 0, "'format' is a magic reserved API param for specifying output format!" ); \
KeyValues *pkvParam = pkvParams->FindKey( pchName, true ); \
pkvParam->SetString( "description", pchDescription ); \
pkvParam->SetInt( "type", eType ); \
pkvParam->SetInt( "optional", 0 ); \
}
#define OPTIONAL_GCWEBAPI_PARAM( pchName, eType, pchDescription ) \
{ \
KeyValues *pkvParams = pkvMethod->FindKey( "params", true ); \
AssertMsg( Q_stricmp( pchName, "format" ) != 0, "'format' is a magic reserved API param for specifying output format!" ); \
KeyValues *pkvParam = pkvParams->FindKey( pchName, true ); \
pkvParam->SetString( "description", pchDescription ); \
pkvParam->SetInt( "type", eType ); \
pkvParam->SetInt( "optional", 1 ); \
}
#define END_GCWEBAPI_METHOD() \
}
#define END_GCWEB_INTERFACE_BLOCK( pchInterfaceName ) \
return pkvInterface; \
} \
CGCWebAPIInterfaceMapRegistrar g_Register_GCWebAPIInterfaceMapCreator_##pchInterfaceName( &CreateWebAPIInterfaceMap_##pchInterfaceName );
#include "tier0/memdbgoff.h"
#endif // GCWEBAPI_H
+45
View File
@@ -0,0 +1,45 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: header for Web API key
//
//=============================================================================
#ifndef GCWEBAPIKEY_H
#define GCWEBAPIKEY_H
#ifdef _WIN32
#pragma once
#endif
using GCSDK::CGCMsgBase;
using GCSDK::WebAPIKey_t;
using GCSDK::EWebAPIKeyStatus;
class CMsgWebAPIKey;
class CWebAPIKey
{
public:
CWebAPIKey() { Clear(); }
void Clear();
bool BIsValid() const { return (m_unAccountID != 0 || m_unPublisherGroupID != 0) && m_eStatus == GCSDK::k_EWebAPIKeyValid; }
bool BIsAccountKey() const { return m_unAccountID != 0; }
bool BIsPublisherKey() const { return m_unPublisherGroupID != 0; }
uint32 GetAccountID() const { return m_unAccountID; }
uint32 GetPublisherGroupID() const { return m_unPublisherGroupID; }
uint32 GetID() const { return m_unWebAPIKeyID; }
const char *GetDomain() const { return m_sDomain; }
EWebAPIKeyStatus GetStatus() const { return m_eStatus; }
void SerializeIntoProtoBuf( CMsgWebAPIKey & apiKey ) const;
void DeserializeFromProtoBuf( const CMsgWebAPIKey & apiKey );
private:
EWebAPIKeyStatus m_eStatus;
uint32 m_unAccountID; // set if key is for an account, 0 otherwise
uint32 m_unPublisherGroupID; // set if key is for a publisher, 0 otherwise
uint32 m_unWebAPIKeyID;
CUtlString m_sDomain;
};
#endif // GCWEBAPIKEY_H
+146
View File
@@ -0,0 +1,146 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef GCWGJOB_H
#define GCWGJOB_H
#ifdef _WIN32
#pragma once
#endif
namespace GCSDK
{
class CGCWGJobMgr;
// defines a single parameter to a web api func
struct WebApiParam_t
{
const char *m_pchParam;
const char *m_pchDescription;
bool m_bOptional; // true if optional
};
//-----------------------------------------------------------------------------
// Privilege type for WG requests
// NOTE: This enum is a copy of EWegApiPrivilege from servercommon.h.
enum EGCWebApiPrivilege
{
k_EGCWebApiPriv_None = 0, // doens't require any privileges
k_EGCWebApiPriv_Account = 1, // user must have a Steam account with password set
k_EGCWebApiPriv_Approved = 2, // user must not be blocked from community activity
k_EGCWebApiPriv_Session = 3, // user must have a current Steam3 session
k_EGCWebApiPriv_Support = 4, // user must have Support flag set
k_EGCWebApiPriv_Admin = 5, // user must have Admin flag set
//////////////////////////////////////////////////////////////////////////
// Steamworks Application Editing -
//
// This represents a minimal requirement - The user must have some of the
// EAppRights available to his account for a particular application.
// This value is stored in the g_WebApiFuncs table.
//
// The functions dispatched to from the g_WebApiFuncs table are responsible
// for doing finer grain permissions checks.
// At this time, only the k_EAppRightManageCEG check is performed at the coarser grain.
//
// Some privileges such k_EAppRightEditInfo are implemented entirely within the
// Web Server's .php code, as these rights do not manipulate data through the Web Gateway,
// but manipulate through direct access to file system files and perforce operations !
//
k_EGCWebApiPriv_EditApp = 6, // user has some rights onto specific app - is publisher-affiliated and rights match app (or is admin)
//
// End Steamworks Application Editing -
//
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Steamworks Publisher Editing -
//
// These represent requests for particular rights involving the manipulation of
// publisher data. k_EGCWebApiPriv_MemberPublisher only requires that the user be a member of a publisher,
// whereas k_EGCWebApiPriv_EditPublisher specifically requires the user has k_EPubRightManagerUsers permission
// within a particular publisher. That is because at this time k_EPubRightManagerUsers is the ONLY
// right defined so the coarse grain view of k_EGCWebApiPriv_EditPublisher exactly matches the finer grain
// view defined by EPubRights.
//
k_EGCWebApiPriv_MemberPublisher = 7, // user is publisher-affiliated with specific publisher (or is admin)
k_EGCWebApiPriv_EditPublisher = 8, // user can edit specific publisher (or is admin)
//
// End Steamworks Publisher Editing -
//
//////////////////////////////////////////////////////////////////////////
k_EGCWebApiPriv_AccountOptional = 9, // validate the token if we get one but also allow public requests through
};
//-----------------------------------------------------------------------------
struct WebApiFunc_t
{
const char *m_pchRequestName;
const char *m_pchRequestHandlerJobName;
EGCWebApiPrivilege m_eRequiredPrivilege;
WebApiParam_t m_rgParams[20];
};
class CGCWGJobMgr
{
public:
CGCWGJobMgr( );
~CGCWGJobMgr();
bool BHandleMsg( IMsgNetPacket *pNetPacket );
static CUtlDict< const WebApiFunc_t* > &GetWGRequestMap();
static void SendErrorMessage( const CGCMsg<MsgGCWGRequest_t> & msg, const char *pchErrorMsg, int32 nResult );
static void SetErrorMessage( KeyValues *pkvErr, const char *pchErrorMsg, int32 nResult );
static void SendResponse( const CGCMsg<MsgGCWGRequest_t> & msg, KeyValues *pkvResponse, bool bResult );
#ifdef DBGFLAG_VALIDATE
virtual void Validate( CValidator &validator, const char *pchName ); // Validate our internal structures
static void ValidateStatics( CValidator &validator );
#endif // DBGFLAG_VALIDATE
protected:
static void RegisterWGJob( const WebApiFunc_t *pWGJobType, const JobType_t *pJobCreationFunc );
friend void GCWGJob_RegisterWGJobType( const WebApiFunc_t *pWGJobType, const JobType_t *pJobCreationFunc );
bool BVerifyPrivileges( const CGCMsg<MsgGCWGRequest_t> & msg, const WebApiFunc_t * pFunc );
bool BVerifyParams( const CGCMsg<MsgGCWGRequest_t> & msg, const WebApiFunc_t * pFunc );
};
inline void GCWGJob_RegisterWGJobType( const WebApiFunc_t *pWGJobType, const JobType_t *pJobCreationFunc )
{
CGCWGJobMgr::RegisterWGJob( pWGJobType, pJobCreationFunc );
}
// declares a job as a wg job, require/optional params should be placed between begin and end declare.
#define DECLARE_GCWG_JOB( gcbaseSubclass, jobclass, requestname, requiredprivilege ) \
CJob *CreateWGJob_##jobclass( gcbaseSubclass *pvParent, void * pvStartParam ); \
static const JobType_t g_JobType_##jobclass = { #jobclass, k_EGCMsgInvalid, k_EServerTypeGC, (JobCreationFunc_t)CreateWGJob_##jobclass }; \
CJob *CreateWGJob_##jobclass( gcbaseSubclass *pvParent, void * pvStartParam ) \
{ \
CJob *job = CJob::AllocateJob<jobclass>( pvParent ); \
Job_SetJobType( *job, &g_JobType_##jobclass ); \
if ( pvStartParam ) job->SetStartParam( pvStartParam ); \
return job; \
} \
static const WebApiFunc_t g_WGRequestInfo_##jobclass = { requestname, #jobclass, requiredprivilege, {
#define REQUIRED_GCWG_PARAM( pstrParameter, pstrDescription ) { pstrParameter, pstrDescription, false },
#define OPTIONAL_GCWG_PARAM( pstrParameter, pstrDescription ) { pstrParameter, pstrDescription, true },
#define END_DECLARE_GCWG_JOB( jobclass ) } }; \
static class CRegWGJob_##jobclass \
{ \
public: CRegWGJob_##jobclass() \
{ \
GCWGJob_RegisterWGJobType( &g_WGRequestInfo_##jobclass, &g_JobType_##jobclass ); \
} \
} g_RegWGJob_##jobclass;
// quick and dirty - register a job with no required/optional parameters
#define REG_GCWG_JOB( jobclass, requestname, requiredprivilege ) \
DECLARE_WG_JOB( jobclass, requestname, requiredprivilege ) \
END_DECLARE_GCWG_JOB( jobclass )
} // namespace GCSDK
#endif
+228
View File
@@ -0,0 +1,228 @@
//====== Copyright © 1996-2010, Valve Corporation, All rights reserved. =======
//
// Purpose: HTTP related enums and objects, stuff that both clients and server use should go here
//
//=============================================================================
#ifndef HTTP_H
#define HTTP_H
#ifdef _WIN32
#pragma once
#endif
#include "steam/steamhttpenums.h"
#include "tier1/KeyValues.h"
#include "tier1/netadr.h"
#include "gcsdk/bufferpool.h"
class CMsgHttpRequest;
class CMsgHttpResponse;
#include "tier0/memdbgon.h"
namespace GCSDK
{
// Container class for useful parsing methods and other utility code used by client or server http code
class CHTTPUtil
{
public:
// Check if a status code allows a body to exist
static bool BStatusCodeAllowsBody( EHTTPStatusCode eHTTPStatus );
};
class CHTTPRequest;
class CHTTPResponse;
class CHTTPServerClientConnection;
class CHTTPRequest
{
public:
CHTTPRequest();
CHTTPRequest( CMsgHttpRequest* pProto );
CHTTPRequest( EHTTPMethod eMethod, const char *pchHost, const char *pchRelativeURL );
CHTTPRequest( EHTTPMethod eMethod, const char *pchAbsoluteURL );
~CHTTPRequest();
// Get the method type for the request (ie, GET, POST, etc)
EHTTPMethod GetEHTTPMethod() const { return ( EHTTPMethod )m_pProto->request_method(); }
// Get the relative URL for the request
const char *GetURL() const { return m_pProto->url().c_str(); }
// Get the value of a GET parameter, using the default value if not set. This is case-insensitive by default.
const CMsgHttpRequest_QueryParam *GetGETParam( const char *pchGetParamName, bool bMatchCase = false ) const;
const char *GetGETParamString( const char *pchGetParamName, const char *pchDefault, bool bMatchCase = false ) const;
bool GetGETParamBool( const char *pchGetParamName, bool bDefault, bool bMatchCase = false ) const;
int32 GetGETParamInt32( const char *pchGetParamName, int32 nDefault, bool bMatchCase = false ) const;
uint32 GetGETParamUInt32( const char *pchGetParamName, uint32 unDefault, bool bMatchCase = false ) const;
int64 GetGETParamInt64( const char *pchGetParamName, int64 nDefault, bool bMatchCase = false ) const;
uint64 GetGETParamUInt64( const char *pchGetParamName, uint64 unDefault, bool bMatchCase = false ) const;
float GetGETParamFloat( const char *pchGetParamName, float fDefault, bool bMatchCase = false ) const;
// Get the value of a POST parameter, using the default value if not set. This is case-insensitive by default.
const CMsgHttpRequest_QueryParam *GetPOSTParam( const char *pchPostParamName, bool bMatchCase = false ) const;
const char *GetPOSTParamString( const char *pchGetParamName, const char *pchDefault, bool bMatchCase = false ) const;
bool GetPOSTParamBool( const char *pchGetParamName, bool bDefault, bool bMatchCase = false ) const;
int32 GetPOSTParamInt32( const char *pchGetParamName, int32 nDefault, bool bMatchCase = false ) const;
uint32 GetPOSTParamUInt32( const char *pchGetParamName, uint32 unDefault, bool bMatchCase = false ) const;
int64 GetPOSTParamInt64( const char *pchGetParamName, int64 nDefault, bool bMatchCase = false ) const;
uint64 GetPOSTParamUInt64( const char *pchGetParamName, uint64 unDefault, bool bMatchCase = false ) const;
float GetPOSTParamFloat( const char *pchGetParamName, float fDefault, bool bMatchCase = false ) const;
// Add a GET param to the request
void SetGETParamString( const char *pchGetParamName, const char *pString ) { SetGETParamRaw( pchGetParamName, (uint8*)pString, Q_strlen(pString) ); }
void SetGETParamBool( const char *pchPostParamName, bool bValue ) { SetGETParamRaw( pchPostParamName, (uint8*)(bValue ? "1" : "0"), 1 ); }
void SetGETParamInt32( const char *pchPostParamName, int32 nValue ) { CNumStr str( nValue ); SetGETParamRaw( pchPostParamName, (uint8*)str.String(), Q_strlen( str ) ); }
void SetGETParamUInt32( const char *pchPostParamName, uint32 unValue ) { CNumStr str( unValue ); SetGETParamRaw( pchPostParamName, (uint8*)str.String(), Q_strlen( str ) ); }
void SetGETParamInt64( const char *pchPostParamName, int64 nValue ) { CNumStr str( nValue ); SetGETParamRaw( pchPostParamName, (uint8*)str.String(), Q_strlen( str ) ); }
void SetGETParamUInt64( const char *pchPostParamName, uint64 unValue ) { CNumStr str( unValue ); SetGETParamRaw( pchPostParamName, (uint8*)str.String(), Q_strlen( str ) ); }
void SetGETParamFloat( const char *pchPostParamName, float fValue ) { CNumStr str( fValue ); SetGETParamRaw( pchPostParamName, (uint8*)str.String(), Q_strlen( str ) ); }
// Add a POST param to the request given a string for the name and value
void SetPOSTParamString( const char *pchPostParamName, const char *pString ) { SetPOSTParamRaw( pchPostParamName, (uint8*)pString, Q_strlen(pString) ); }
void SetPOSTParamBool( const char *pchPostParamName, bool bValue ) { SetPOSTParamRaw( pchPostParamName, (uint8*)(bValue ? "1" : "0"), 1 ); }
void SetPOSTParamInt32( const char *pchPostParamName, int32 nValue ) { CNumStr str( nValue ); SetPOSTParamRaw( pchPostParamName, (uint8*)str.String(), Q_strlen( str ) ); }
void SetPOSTParamUInt32( const char *pchPostParamName, uint32 unValue ) { CNumStr str( unValue ); SetPOSTParamRaw( pchPostParamName, (uint8*)str.String(), Q_strlen( str ) ); }
void SetPOSTParamInt64( const char *pchPostParamName, int64 nValue ) { CNumStr str( nValue ); SetPOSTParamRaw( pchPostParamName, (uint8*)str.String(), Q_strlen( str ) ); }
void SetPOSTParamUInt64( const char *pchPostParamName, uint64 unValue ) { CNumStr str( unValue ); SetPOSTParamRaw( pchPostParamName, (uint8*)str.String(), Q_strlen( str ) ); }
void SetPOSTParamFloat( const char *pchPostParamName, float fValue ) { CNumStr str( fValue ); SetPOSTParamRaw( pchPostParamName, (uint8*)str.String(), Q_strlen( str ) ); }
// Adds a POST param containing raw data to the request. If you are using the Web API, you probably do not want this function
void SetPOSTParamRaw( const char *pchPostParamName, uint8 *pData, uint32 cubDataLen );
// Raw iteration support for GET and POST parameters
uint32 GetPOSTParamCount() const { return m_pProto->post_params_size(); }
const char* GetPOSTParamName( uint32 i ) const { return m_pProto->post_params( i ).name().c_str(); }
const char* GetPOSTParamValue( uint32 i ) const { return m_pProto->post_params( i ).value().c_str(); }
uint32 GetGETParamCount() const { return m_pProto->get_params_size(); }
const char* GetGETParamName( uint32 i ) const { return m_pProto->get_params( i ).name().c_str(); }
const char* GetGETParamValue( uint32 i ) const { return m_pProto->get_params( i ).value().c_str(); }
// Fetch a request header by header name and convert it to a time value
RTime32 GetRequestHeaderTimeValue( const char *pchRequestHeaderName, RTime32 rtDefault = 0 ) const;
// Fetch a request headers value by header name
const char *GetRequestHeaderValue( const char *pchRequestHeaderName, const char *pchDefault = NULL ) const;
// Set a header field for the request
void SetRequestHeaderValue( const char *pchHeaderName, const char *pchHeaderString );
// Set the method for the request object
void SetEHTTPMethod( EHTTPMethod eMethod ) { m_pProto->set_request_method( eMethod ); }
// Set the relative URL for the request
void SetURL( const char *pchURL )
{
AssertMsg( pchURL && pchURL[0] == '/', "URLs must start with the slash (/) character. Param: %s", pchURL );
m_pProto->set_url( pchURL );
}
void SetURLDirect( const char *pchURL, size_t size )
{
AssertMsg( pchURL && pchURL[0] == '/', "URLs must start with the slash (/) character. Param: %s", pchURL );
m_pProto->set_url( pchURL, size );
}
// Set body data
void SetBodyData( const void *pubData, uint32 cubData );
bool BHasBodyData() { return m_pProto->has_body(); }
uint GetBodyDataSize() const { return (uint)m_pProto->body().size(); }
const char* GetBodyData() const { return m_pProto->body().c_str(); }
// Set hostname for request to target (or host it was received on)
void SetHostname( const char *pchHost ) { m_pProto->set_hostname( pchHost ); }
void SetHostnameDirect( const char *pchHost, uint32 unLength ) { m_pProto->set_hostname( pchHost, unLength ); }
const char *GetHostname() const { return m_pProto->hostname().c_str(); }
// Set the overall timeout in seconds for this request
void SetAbsoluteTimeoutSeconds( uint32 unSeconds ) { m_pProto->set_absolute_timeout( unSeconds ); }
// Get the total absolute timeout value for the request
uint32 GetAbsoluteTimeoutSeconds() { return m_pProto->absolute_timeout(); }
//accesses the HTTP message
const CMsgHttpRequest& GetProtoObj() const { return *m_pProto; }
static void RetrieveURLEncodedData( const char *pchQueryString, int nQueryStrLen, CUtlVector<CMsgHttpRequest_QueryParam> &vecParams );
protected:
// Adds a GET param containing raw data to the request. If you are using the Web API, you probably do not want this function
void SetGETParamRaw( const char *pchGetParamName, uint8 *pData, uint32 cubDataLen );
// Common initialization code
void Init( CMsgHttpRequest* pProto );
//the protobuf object that stores all of our contents
CMsgHttpRequest* m_pProto;
//whether or not we own this proto and should free it when the object is destroyed
bool m_bOwnProto;
};
//-----------------------------------------------------------------------------
// Purpose: A wrapper to CHTTPRequest for Steam WebAPIs. Host, mode, and API key
// are set automatically
//-----------------------------------------------------------------------------
class CSteamAPIRequest : public CHTTPRequest
{
public:
CSteamAPIRequest( EHTTPMethod eMethod, const char *pchInterface, const char *pchMethod, int nVersion );
};
class CHTTPResponse
{
public:
CHTTPResponse();
virtual ~CHTTPResponse();
// Get a specific headers data
const char *GetResponseHeaderValue( const char *pchName, const char *pchDefault = NULL ) const { return m_pkvResponseHeaders->GetString( pchName, pchDefault ); }
// Set a specific headers data, will clobber any existing value for that header
virtual void SetResponseHeaderValue( const char *pchName, const char *pchValue ) { m_pkvResponseHeaders->SetString( pchName, pchValue ); }
// Set status code for response
virtual void SetStatusCode( EHTTPStatusCode eStatusCode ) { m_eStatusCode = eStatusCode; }
// Set the expiration header based on a time delta from now
void SetExpirationHeaderDeltaFromNow( int32 nSecondsFromNow );
// Set the expiration header based on a time delta from now
void SetHeaderTimeValue( const char *pchHeaderName, RTime32 rtTimestamp );
// Get the entire headers KV (in case you want to iterate them all or such)
KeyValues *GetResponseHeadersKV() const { return m_pkvResponseHeaders; }
// Accessors to the body data in the response
const uint8 *GetPubBody() const { return (uint8*)m_pbufBody->Base(); }
uint32 GetCubBody() const { return m_pbufBody->TellPut(); }
const CUtlBuffer *GetBodyBuffer() const { return m_pbufBody; }
CUtlBuffer *GetBodyBuffer() { return m_pbufBody; }
// Accessor
EHTTPStatusCode GetStatusCode() const { return m_eStatusCode; }
// writes the response into a protobuf for use in messages
void SerializeIntoProtoBuf( CMsgHttpResponse & response ) const;
// reads the response out of a protobuf from a message
void DeserializeFromProtoBuf( const CMsgHttpResponse & response );
protected:
static CBufferPool &GetBufferPool();
EHTTPStatusCode m_eStatusCode;
CUtlBuffer *m_pbufBody;
KeyValues *m_pkvResponseHeaders;
};
}
#include "tier0/memdbgoff.h"
#endif // HTTP_H
+465
View File
@@ -0,0 +1,465 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================
#ifndef GC_JOB_H
#define GC_JOB_H
#ifdef _WIN32
#pragma once
#endif
#include "tier0/memdbgon.h"
#include "tier1/functors.h"
#include "workthreadpool.h"
namespace GCSDK
{
class CJobMgr;
class CLock;
class CJob;
class IMsgNetPacket;
//-----------------------------------------------------------------------------
// Purpose: Use these macros to declare blocks where it is unsafe to yield.
// The job will assert if it pauses within the block
//-----------------------------------------------------------------------------
#define DO_NOT_YIELD_THIS_SCOPE() GCSDK::CDoNotYieldScope doNotYieldScope_##line( FILE_AND_LINE )
#define BEGIN_DO_NOT_YIELD() GJobCur().PushDoNotYield( FILE_AND_LINE )
#define END_DO_NOT_YIELD() GJobCur().PopDoNotYield()
class CDoNotYieldScope
{
public:
CDoNotYieldScope( const char *pchLocation );
~CDoNotYieldScope();
private:
// Disallow these constructors and operators
CDoNotYieldScope();
CDoNotYieldScope( const CDoNotYieldScope &that );
CDoNotYieldScope &operator=( const CDoNotYieldScope &that );
};
//-----------------------------------------------------------------------------
// job creation function
typedef CJob *(*JobCreationFunc_t)( void *pvServerParent, void * pvStartParam );
//-----------------------------------------------------------------------------
// Purpose: static job information
// contains information relevant to one type of CJob
//-----------------------------------------------------------------------------
struct JobType_t
{
const char *m_pchName; // name of this type of job
MsgType_t m_eCreationMsg; // network message that creates this job
EServerType m_eServerType; // the server type that responds to this message
JobCreationFunc_t m_pJobFactory; // virtual constructor
};
//-----------------------------------------------------------------------------
// Purpose: reason as to why the current job has yielded to the main thread (paused)
// if this is updated, k_prgchJobPauseReason[] in job.cpp also needs to be updated
//-----------------------------------------------------------------------------
enum EJobPauseReason
{
k_EJobPauseReasonNone,
k_EJobPauseReasonNotStarted,
k_EJobPauseReasonNetworkMsg,
k_EJobPauseReasonSleepForTime,
k_EJobPauseReasonWaitingForLock,
k_EJobPauseReasonYield,
k_EJobPauseReasonSQL,
k_EJobPauseReasonWorkItem,
k_EJobPauseReasonCount
};
//-----------------------------------------------------------------------------
// Purpose: contains information used to route a message to a job, or to
// create a new job from that message type
//-----------------------------------------------------------------------------
struct JobMsgInfo_t
{
MsgType_t m_eMsg;
JobID_t m_JobIDSource;
JobID_t m_JobIDTarget;
EServerType m_eServerType;
JobMsgInfo_t()
{
m_eMsg = (MsgType_t)0;
m_JobIDSource = k_GIDNil;
m_JobIDTarget = k_GIDNil;
m_eServerType = k_EServerTypeInvalid;
}
JobMsgInfo_t( MsgType_t eMsg, JobID_t jobIDSource, JobID_t jobIDTarget, EServerType eServerType )
{
m_eMsg = eMsg;
m_JobIDSource = jobIDSource;
m_JobIDTarget = jobIDTarget;
m_eServerType = eServerType;
}
};
typedef void (CJob::*JobThreadFunc_t)();
#define BYieldingAcquireLock( lock ) _BYieldingAcquireLock( lock, __FILE__, __LINE__ )
#define BAcquireLockImmediate( lock ) _BAcquireLockImmediate( lock, __FILE__, __LINE__ )
#define ReleaseLock( lock ) _ReleaseLock( lock, false, __FILE__, __LINE__ )
//-----------------------------------------------------------------------------
// Purpose: A job is any server operation that requires state. Typically, we use jobs for
// operations that need to pause waiting for responses from other servers. The
// job object persists the state of the operation while it waits, and the reply
// from the remote server re-activates the job.
//-----------------------------------------------------------------------------
class CJob
{
public:
// Constructors & destructors, when overriding job name a static string pointer must be used
explicit CJob( CJobMgr &jobMgr, char const *pchJobName = NULL );
virtual ~CJob();
// starts the job, storing off the network msg and calling it's Run() function
void StartJobFromNetworkMsg( IMsgNetPacket *pNetPacket, const JobID_t &gidJobIDSrc );
// accessors
JobID_t GetJobID() const { return m_JobID; }
// start job immediately
// mostly for CMD jobs, which should immediately Yield() once
// so that they don't do their work until the enclosing JobMbr.Run()
// is called
void StartJob( void * pvStartParam );
// schedules the job for execution, but does not interrup the currently running job. Effectively starts the job on the yielding list as if it had immediately yielded
// although is more efficient than actually doing so
void StartJobDelayed( void * pvStartParam );
// string name of the job
const char *GetName() const;
// return reason why we're paused
EJobPauseReason GetPauseReason() const { return m_ePauseReason; }
// string description of why we're paused
const char *GetPauseReasonDescription() const;
// return time at which this job was last paused or continued
const CJobTime& GetTimeSwitched() const { return m_STimeSwitched; }
// return microseconds run since we were last continued
uint64 GetMicrosecondsRun() const { return m_FastTimerDelta.GetDurationInProgress().GetUlMicroseconds(); }
bool BJobNeedsToHeartbeat() const { return ( m_STimeNextHeartbeat.CServerMicroSecsPassed() >= 0 ); }
// --- locking pointers
bool _BYieldingAcquireLock( CLock *pLock, const char *filename = "unknown", int line = 0 );
bool _BAcquireLockImmediate( CLock *pLock, const char *filename = "unknown", int line = 0 );
void _ReleaseLock( CLock *pLock, bool bForce = false, const char *filename = "unknown", int line = 0 );
bool BHoldsAnyLocks() const { return m_vecLocks.Count() > 0; }
void ReleaseLocks();
/// If we hold any locks, spew about them and release them.
/// This is useful for long running jobs, to make sure they don't leak
/// locks that never get cleaned up
void ShouldNotHoldAnyLocks();
// --- general methods for waiting for events
// Simple yield to other jobs until Run() called again
bool BYield();
// Yield IF JobMgr thinks we need to based on how long we've run and our priority
bool BYieldIfNeeded( bool *pbYielded = NULL );
// waits for a set amount of time
bool BYieldingWaitTime( uint32 m_cMicrosecondsToSleep );
bool BYieldingWaitOneFrame();
// waits for another network msg, returning false if none returns
bool BYieldingWaitForMsg( IMsgNetPacket **ppNetPacket );
bool BYieldingWaitForMsg( CGCMsgBase *pMsg, MsgType_t eMsg );
bool BYieldingWaitForMsg( CProtoBufMsgBase *pMsg, MsgType_t eMsg );
#ifdef GC
bool BYieldingWaitForMsg( CGCMsgBase *pMsg, MsgType_t eMsg, const CSteamID &expectedID );
bool BYieldingWaitForMsg( CProtoBufMsgBase *pMsg, MsgType_t eMsg, const CSteamID &expectedID );
bool BYieldingRunQuery( CGCSQLQueryGroup *pQueryGroup, ESchemaCatalog eSchemaCatalog );
#endif
bool BYieldingWaitTimeWithLimit( uint32 cMicrosecondsToSleep, CJobTime &stimeStarted, int64 nMicroSecLimit );
bool BYieldingWaitTimeWithLimitRealTime( uint32 cMicrosecondsToSleep, int nSecLimit );
void RecordWaitTimeout() { m_flags.m_bits.m_bWaitTimeout = true; }
// wait for pending work items before deleting job
void WaitForThreadFuncWorkItemBlocking();
// waits for a work item completion callback
// You can pass a string that describes what sort of work item you are waiting on.
// WARNING: This function saves the pointer to the string, it doesn't copy the string
bool BYieldingWaitForWorkItem( const char *pszWorkItemName = NULL );
// adds this work item to threaded work pool and waits for it
bool BYieldingWaitForThreadFuncWorkItem( CWorkItem * );
// calls a local function in a thread, and yields until it's done
bool BYieldingWaitForThreadFunc( CFunctor *jobFunctor );
// Used by the DO_NOT_YIELD() macros
int32 GetDoNotYieldDepth() const;
void PushDoNotYield( const char *pchFileAndLine );
void PopDoNotYield();
#ifdef DBGFLAG_VALIDATE
virtual void Validate( CValidator &validator, const char *pchName ); // Validate our internal structures
static void ValidateStatics( CValidator &validator, const char *pchName );
#endif
// creates a job
template <typename JOB_TYPE, typename PARAM_TYPE>
static JOB_TYPE *AllocateJob( PARAM_TYPE *pParam )
{
return new JOB_TYPE( pParam );
}
// delete a job (the job knows what allocator to use)
static void DeleteJob( CJob *pJob );
void SetStartParam( void * pvStartParam ) { Assert( NULL == m_pvStartParam ); m_pvStartParam = pvStartParam; }
void SetFromFromMsg( bool bRunFromMsg ) { m_bRunFromMsg = true; }
void AddPacketToList( IMsgNetPacket *pNetPacket, const JobID_t gidJobIDSrc );
// marks a packet as being finished with, releases the packet and frees the memory
void ReleaseNetPacket( IMsgNetPacket *pNetPacket );
void EndPause( EJobPauseReason eExpectedState );
// Generate an assertion in the coroutine of this job
// (creating a minidump). Useful for inspecting stuck jobs
void GenerateAssert( const char *pchMsg = NULL );
/// Return true if we tried to waited on a message of this type,
/// and failed to receive it. (If so, then this means we could
/// conceivably still receive a reply of that type at any moment.)
bool BHasFailedToReceivedMsgType( MsgType_t m ) const;
/// Mark that we awaited a message of the specified type, but timed out
void MarkFailedToReceivedMsgType( MsgType_t m );
/// Clear flag that we timed out waiting on a message of the specified type.
/// This is used to allow you to wait ont he same message again, even if
/// you know the reply might be a late reply. It's up to you to deal with
/// mismatched replies!
void ClearFailedToReceivedMsgType( MsgType_t m );
protected:
// main job implementation, in the coroutine. Every job must implement at least one of these methods.
virtual bool BYieldingRunJob( void * pvStartParam ) { Assert( false ); return true; } // implement this if your job can be started directly
virtual bool BYieldingRunJobFromMsg( IMsgNetPacket * pNetPacket ) { Assert( false ); return true; } // implement this if your job can be started by a network message
// Can be overridden to return a different timeout per job class
virtual uint32 CHeartbeatsBeforeTimeout();
// Called by CJobMgr to send heartbeat message to our listeners during long operations
void Heartbeat();
// accessor to get access to the JobMgr from the server we belong to
CJobMgr &GetJobMgr();
uint32 m_bRunFromMsg:1,
m_bWorkItemCanceled:1, // true if the work item we were waiting on was canceled
m_bIsTest:1,
m_bIsLongRunning:1;
private:
// starts the coroutine that activates the job
void InitCoroutine();
// continues the current job
void Continue();
// break into this coroutine - can only be called from OUTSIDE this coroutine
void Debug();
// pauses the current job - can only be called from inside a coroutine
void Pause( EJobPauseReason eReason );
static void BRunProxy( void *pvThis );
JobID_t m_JobID; // Our unique identifier.
HCoroutine m_hCoroutine;
void * m_pvStartParam; // Start params for our job, if any
// all these flags indicate some kind of failure and we will want to report them
union {
struct {
uint32
m_bJobFailed:1, // true if BYieldingRunJob returned false
m_bLocksFailed:1,
m_bLocksLongHeld:1,
m_bLocksLongWait:1,
m_bWaitTimeout:1,
m_bLongInterYield:1,
m_bTimeoutNetMsg:1,
m_bTimeoutOther:1,
m_uUnused:24;
} m_bits;
uint32 m_uFlags;
} m_flags;
int m_cLocksAttempted;
int m_cLocksWaitedFor;
EJobPauseReason m_ePauseReason;
MsgType_t m_unWaitMsgType;
CJobTime m_STimeStarted; // time (frame) at which this job started
CJobTime m_STimeSwitched; // time (frame) at which we were last paused or continued
CJobTime m_STimeNextHeartbeat; // Time at which next heartbeat should be performed
CFastTimer m_FastTimerDelta; // How much time we've been running for without yielding
CCycleCount m_cyclecountTotal; // Total runtime
CJob *m_pJobPrev; // the job that launched us
// lock manipulation
void _SetLock( CLock *pLock, const char *filename, int line );
void UnsetLock( CLock *pLock );
void PassLockToJob( CJob *pNewJob, CLock *pLock );
void OnLockDeleted( CLock *pLock );
void AddJobToNotifyOnLockRelease( CJob *pJob );
CUtlVectorFixedGrowable< CLock *, 2 > m_vecLocks;
CLock *m_pWaitingOnLock; // lock we're waiting on, if any
const char *m_pWaitingOnLockFilename;
int m_waitingOnLockLine;
CJob *m_pJobToNotifyOnLockRelease; // other job that wants this lock
CWorkItem *m_pWaitingOnWorkItem; // set if job is waiting for this work item
CJobMgr &m_JobMgr; // our job manager
CUtlVectorFixedGrowable< IMsgNetPacket *, 1 > m_vecNetPackets; // list of tcp packets currently held by this job (ie, needing release on job exit)
/// List of message types that we have waited for, but timed out.
/// once this happens, we currently do not have a good mechanism
/// to recover gracefully. But we at least can detect the situation
/// and avoid getting totally hosed or processing the wrong reply
CUtlVector<MsgType_t> m_vecMsgTypesFailedToReceive;
// pointer to our own static job info
struct JobType_t const *m_pJobType;
// Name of the job for when it's not registered
const char *m_pchJobName;
// A stack of do not yield guards so we can print the right warning if they're nested
CUtlLinkedList<const char *> m_stackDoNotYieldGuards;
// setting the job info
friend void Job_SetJobType( CJob &job, const JobType_t *pJobType );
friend class CJobMgr;
friend class CLock;
// used to store the memory allocation stack
CUtlMemory< unsigned char > m_memAllocStack;
};
// Only one job can be running at a time. We keep a global accessor to it.
extern CJob *g_pJobCur;
inline CJob &GJobCur() { Assert( g_pJobCur != NULL ); return *g_pJobCur; }
#define AssertRunningJob() { Assert( NULL != g_pJobCur ); }
#define AssertRunningThisJob() { Assert( this == g_pJobCur ); }
#define AssertNotRunningThisJob() { Assert( this != g_pJobCur ); }
#define AssertNotRunningJob() { Assert( NULL == g_pJobCur ); }
//-----------------------------------------------------------------------------
// Purpose: simple locking class
// add this object to any classes you want jobs to be able to lock
//-----------------------------------------------------------------------------
class CLock
{
public:
CLock( );
~CLock();
bool BIsLocked() { return m_pJob != NULL; }
CJob *GetJobLocking() { return m_pJob; }
CJob *GetJobWaitingQueueHead() { return m_pJobToNotifyOnLockRelease; }
CJob *GetJobWaitingQueueTail() { return m_pJobWaitingQueueTail; }
void AddToWaitingQueue( CJob *pJob );
const char *GetName() const;
void SetName( const char *pchName );
void SetName( const char *pchPrefix, uint64 ulID );
void SetName( const CSteamID &steamID );
int16 GetLockType() const { return m_nsLockType; }
void SetLockType( int16 nsLockType ) { m_nsLockType = nsLockType; }
uint64 GetLockSubType() const { return m_unLockSubType; }
void SetLockSubType( uint64 unLockSubType ) { m_unLockSubType = unLockSubType; }
int32 GetWaitingCount() const { return m_nWaitingCount; }
int64 GetMicroSecondsSinceLock() const { return m_sTimeAcquired.CServerMicroSecsPassed(); }
void IncrementReference();
int DecrementReference();
void ClearReference() { m_nRefCount = 0; }
int32 GetReferenceCount() const { return m_nRefCount; }
void Dump( const char *pszPrefix = "\t\t", int nPrintMax = 1, bool bPrintWaiting = true ) const;
private:
enum ENameType
{
k_ENameTypeNone = 0,
k_ENameTypeSteamID = 1,
k_ENameTypeConstStr = 2,
k_ENameTypeConcat = 3,
};
CJob *m_pJob; // the job that's currently locking us
CJob *m_pJobToNotifyOnLockRelease; // Pointer to the first job waiting on us
CJob *m_pJobWaitingQueueTail; // Pointer to the last job waiting on us
int16 m_nsLockType; // Lock priority for safely waiting on multiple locks
int16 m_nsNameType; // Enum that controls how this lock is named
uint64 m_ulID; // ID part of the lock's name
const char *m_pchConstStr; // Prefix part of the lock's name
mutable CUtlString m_strName; // Cached name
int32 m_nRefCount; // # of times locked
int32 m_nWaitingCount; // Count of jobs waiting on the lock
CJobTime m_sTimeAcquired; // Time the lock was last locked
uint64 m_unLockSubType;
const char *m_pFilename; // Filename of the source file who aquired this lock
int m_line; // Line number of the filename
friend class CJob;
};
//-----------------------------------------------------------------------------
// Purpose: automatic locking class
//-----------------------------------------------------------------------------
class CAutoCLock
{
public:
CAutoCLock( CLock &refLock )
: m_pLock ( &refLock)
{
DbgVerify( GJobCur().BYieldingAcquireLock( m_pLock ) );
}
// explicitly unlock before destruction
void Unlock()
{
if ( m_pLock != NULL )
GJobCur().ReleaseLock( m_pLock );
m_pLock = NULL;
}
~CAutoCLock( )
{
Unlock();
}
private:
CLock *m_pLock;
CJob *m_pJob;
};
} // namespace GCSDK
#include "tier0/memdbgoff.h"
#endif // GC_JOB_H
+430
View File
@@ -0,0 +1,430 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================
#ifndef GC_JOBMGR_H
#define GC_JOBMGR_H
#ifdef _WIN32
#pragma once
#endif
#include "tier0/fasttimer.h"
#include "tier1/utlpriorityqueue.h"
#include "job.h"
#include "workthreadpool.h"
class GCConVar;
#include "tier0/memdbgon.h"
namespace GCSDK
{
#if defined(_DEBUG)
// this is restricted to debug builds due to the performance cost
// that could be changed by removing the expensive sm_listAllJobs.Find() command
#define DEBUG_JOB_LIST
#endif // defined(_DEBUG)
struct JobStats_t
{
uint m_cJobsCurrent;
uint m_cJobsTotal;
uint m_cJobsFailed;
uint64 m_cJobsTimedOut; // # of jobs timed out ever
double m_flSumJobTimeMicrosec;
double m_flSumSqJobTimeMicrosec;
uint64 m_unMaxJobTimeMicrosec;
uint m_cTimeslices;
JobStats_t()
{
memset( this, 0, sizeof(JobStats_t) );
}
};
struct JobStatsBucket_t
{
JobStatsBucket_t()
{
memset( this, 0, sizeof(JobStatsBucket_t) );
}
char m_rgchName[64];
uint64 m_cCompletes;
uint64 m_u64RunTimeMax;
uint64 m_cTimeoutNetMsg;
uint64 m_cLongInterYieldTime;
uint64 m_cLocksAttempted;
uint64 m_cLocksWaitedFor;
uint64 m_cLocksFailed;
uint64 m_cLocksLongHeld;
uint64 m_cLocksLongWait;
uint64 m_cWaitTimeout;
uint64 m_u64JobDuration;
uint64 m_cJobsPaused;
uint64 m_cJobsFailed;
uint64 m_u64RunTime;
// use by ListJobs
uint64 m_cPauseReasonNetworkMsg;
uint64 m_cPauseReasonSleepForTime;
uint64 m_cPauseReasonWaitingForLock;
uint64 m_cPauseReasonYield;
uint64 m_cPauseReasonSQL;
uint64 m_cPauseReasonWorkItem;
#ifdef DBGFLAG_VALIDATE
void Validate( CValidator &validator, const char *pchName )
{
VALIDATE_SCOPE();
}
#endif
};
enum EJobProfileAction
{
k_EJobProfileAction_ErrorReport = 0,
k_EJobProfileAction_Start = 1,
k_EJobProfileAction_Stop = 2,
k_EJobProfileAction_Dump = 3,
k_EJobProfileAction_Clear = 4,
};
enum EJobProfileSortOrder
{
k_EJobProfileSortOrder_Alpha = 0,
k_EJobProfileSortOrder_Count = 1,
k_EJobProfileSortOrder_TotalRuntime = 2,
};
struct JobProfileStats_t
{
int m_iJobProfileSort;
CUtlMap< uint32, JobStatsBucket_t, int > *pmapStatsBucket;
};
//-----------------------------------------------------------------------------
// Purpose: This keeps track of all jobs that belong to a given hub.
// It's primarily used for routing incoming messages to jobs.
//-----------------------------------------------------------------------------
class CJobMgr
{
public:
// Constructors & destructors
CJobMgr();
~CJobMgr();
// gets the next available job ID
JobID_t GetNewJobID();
// Set the thread count for the internal thread pool(s)
void SetThreadPoolSize( uint cThreads );
// Run any sleeping jobs who's wakeup time has arrived and check for timeouts
bool BFrameFuncRunSleepingJobs( CLimitTimer &limitTimer );
// Run any yielding jobs, even low priority ones
bool BFrameFuncRunYieldingJobs( CLimitTimer &limitTimer );
// Route this message to an existing Job, or create a new one if that JobID does not exist
bool BRouteMsgToJob( void *pParent, IMsgNetPacket *pNetPacket, const JobMsgInfo_t &jobMsgInfo );
// Adds a new Job to the mgr and generates a JobID for it.
void InsertJob( CJob &job );
// Removes a Job from the mgr (the caller is still responsible for freeing it)
void RemoveJob( CJob &job );
//called by a job that has just been started to place itself on the yield queue instead of running
void AddDelayedJobToYieldList( CJob &job );
#ifdef GC
// resumes the specified job if it is, in fact, waiting for a SQL query to return
bool BResumeSQLJob( JobID_t jobID );
// yields waiting for a query response
bool BYieldingRunQuery( CJob &job, CGCSQLQueryGroup *pQueryGroup, ESchemaCatalog eSchemaCatalog );
// SQL profiling
enum ESQLProfileSort
{
k_ESQLProfileSortTotalTime,
k_ESQLProfileSortTotalCount,
k_ESQLProfileSortAvgTime,
k_ESQLProfileSortName
};
void StartSQLProfiling();
void StopSQLProfiling();
void DumpSQLProfile( ESQLProfileSort eSort );
#endif
// returns true if we're running any jobs of the specified name
// slow to call if lots of jobs are running, should only be used by tests
bool BIsJobRunning( const char *pchJobName );
// passes a network msg directly to the specified job
void PassMsgToJob( CJob &job, IMsgNetPacket *pNetPacket, const JobMsgInfo_t &jobMsgInfo );
// yields until a network message is received
bool BYieldingWaitForMsg( CJob &job );
// yields for a set amount of time
bool BYieldingWaitTime( CJob &job, uint32 cMicrosecondsToSleep );
// simple yield until Run() called again
bool BYield( CJob &job );
// Yield only if job manager decides we need to
bool BYieldIfNeeded( CJob &job, bool *pbYielded );
// Thread pool work item
bool BYieldingWaitForWorkItem( CJob &job, const char *pszWorkItemName = NULL );
bool BRouteWorkItemCompleted( JobID_t jobID, bool bWorkItemCanceled ) { return BRouteWorkItemCompletedInternal( jobID, bWorkItemCanceled, /* bShouldExist */ true, /* bResumeImmediately */ true ); }
bool BRouteWorkItemCompletedIfExists( JobID_t jobID, bool bWorkItemCanceled ) { return BRouteWorkItemCompletedInternal( jobID, bWorkItemCanceled, /* bShouldExist */ false, /* bResumeImmediately */ true ); }
bool BRouteWorkItemCompletedDelayed( JobID_t jobID, bool bWorkItemCanceled ) { return BRouteWorkItemCompletedInternal( jobID, bWorkItemCanceled, /* bShouldExist */ true, /* bResumeImmediately */ false ); }
bool BRouteWorkItemCompletedIfExistsDelayed( JobID_t jobID, bool bWorkItemCanceled ) { return BRouteWorkItemCompletedInternal( jobID, bWorkItemCanceled, /* bShouldExist */ false, /* bResumeImmediately */ false ); }
void AddThreadedJobWorkItem( CWorkItem *pWorkItem );
void StopWorkThreads() { m_WorkThreadPool.StopWorkThreads(); }
static int ProfileSortFunc( void *pCtx, const int *lhs, const int *rhs );
void ProfileJobs( EJobProfileAction ejobProfileAction, EJobProfileSortOrder iSortOrder = k_EJobProfileSortOrder_Alpha );
int DumpJobSummary();
void DumpJob( JobID_t jobID, int nPrintLocksMax = 20 ) const;
int CountJobs() const; // counts currently active jobs
void CheckThreadID(); // make sure we are still in the correct thread
int CountYieldingJobs() const { return m_ListJobsYieldingRegPri.Count(); } // counts jobs currently in a yielding state
bool HasOutstandingThreadPoolWorkItems();
void SetIsShuttingDown();
bool GetIsShuttingDown() const { return m_bIsShuttingDown; }
void *GetMainMemoryDebugInfo() { return g_memMainDebugInfo.Base(); }
#ifdef DBGFLAG_VALIDATE
void Validate( CValidator &validator, const char *pchName ); // Validate our internal structures
static void ValidateStatics( CValidator &validator, const char *pchName );
#endif /* DBGFLAG_VALIDATE */
// wakes up a job that was waiting on a lock
void WakeupLockedJob( CJob &job );
// returns true if there is a job active with the specified ID
bool BJobExists( JobID_t jobID ) const;
// returns a job
CJob *GetPJob( JobID_t jobID );
const CJob *GetPJob( JobID_t jobID ) const;
JobStats_t& GetJobStats() { return m_JobStats; }
// Access work thread pool directly
CWorkThreadPool *AccessWorkThreadPool() { return &m_WorkThreadPool; }
// Debug helpers
// dumps a list of all running jobs across ALL job managers
void DumpJobs( const char *pszJobName, int nMax, int nPrintLocksMax = 1 ) const;
// cause a debug break in the given job
static void DebugJob( int iJob );
// disable/enable yielding for debugging
void SetPauseAllowed( bool bNewPauseAllowed ) { m_bDebugDisallowPause = !bNewPauseAllowed; }
private:
bool BRouteWorkItemCompletedInternal( JobID_t jobID, bool bWorkItemCanceled, bool bShouldExist, bool bResumeImmediately );
// Create a new job for this message
bool BLaunchJobFromNetworkMsg( void *pParent, const JobMsgInfo_t &jobMsgInfo, IMsgNetPacket *pNetPacket );
// Internal add to yield list (looks at priority)
void AddToYieldList( CJob &job );
// Get an IJob given a job ID and pause reason
bool BGetIJob( JobID_t jobID, EJobPauseReason eJobPauseReason, bool bShouldExist, int *pIJob );
// Map containing all of our jobs
CUtlMap<JobID_t, CJob *, int> m_MapJob;
// jobs simply waiting until the next Run()
struct JobYielding_t
{
JobID_t m_JobID;
uint m_nIteration;
};
CUtlLinkedList<JobYielding_t, int> m_ListJobsYieldingRegPri;
bool BResumeYieldingJobs( CLimitTimer &limitTimer );
bool BResumeYieldingJobsFromList( CUtlLinkedList<JobYielding_t, int> &listJobsYielding, uint nCurrentIteration, CLimitTimer &limitTimer );
uint m_nCurrentYieldIterationRegPri;
// jobs waiting on a timer
struct JobSleeping_t
{
JobID_t m_JobID;
CJobTime m_SWakeupTime;
CJobTime m_STimeTouched;
};
CUtlPriorityQueue<JobSleeping_t> m_QueueJobSleeping;
bool BResumeSleepingJobs( CLimitTimer &limitTimer );
static bool JobSleepingLessFunc( JobSleeping_t const &lhs, JobSleeping_t const &rhs );
// timeout list of jobs, ordered from oldest to newest
struct JobTimeout_t
{
JobID_t m_JobID;
CJobTime m_STimePaused;
CJobTime m_STimeTouched;
uint32 m_cHeartbeatsBeforeTimeout;
};
CUtlLinkedList<JobTimeout_t, int> m_ListJobTimeouts;
CUtlMap<JobID_t, int, int> m_MapJobTimeoutsIndexByJobID;
void PauseJob( CJob &job, EJobPauseReason eJobPauseReason );
void CheckForJobTimeouts( CLimitTimer &limitTimer );
void TimeoutJob( CJob &job );
bool m_bJobTimedOut;
// thread pool usage, for running job functions in other threads
CWorkThreadPool m_WorkThreadPool;
void AccumulateStatsofJob( CJob &job );
void RecordOrphanedMessage( MsgType_t eMsg, JobID_t jobIDTarget );
// stats info
JobStats_t m_JobStats;
// static job registration
static void RegisterJobType( const JobType_t *pJobType );
friend void Job_RegisterJobType( const JobType_t *pJobType );
JobID_t m_unNextJobID;
uint m_unFrameFuncThreadID; // the thread is JobMgr is working in
bool m_bProfiling;
bool m_bIsShuttingDown;
int m_cErrorsToReport;
CUtlMap< uint32, JobStatsBucket_t, int > m_mapStatsBucket;
CUtlMap<MsgType_t, int, int> m_mapOrphanMessages;
CUtlMemory<unsigned char> g_memMainDebugInfo;
#ifdef GC
// sql profiling
bool m_bSQLProfiling;
CFastTimer m_sqlTimer;
struct PendingSQLJob_t
{
int64 m_nStartMicrosec;
int32 m_iBucket;
};
struct SQLProfileBucket_t
{
int64 m_nTotalMicrosec;
uint32 m_unCount;
};
CUtlHashMapLarge<GID_t, PendingSQLJob_t> m_mapSQLQueriesInFlight;
CUtlDict<SQLProfileBucket_t> m_dictSQLBuckets;
struct SQLProfileCtx_t
{
ESQLProfileSort m_eSort;
CUtlDict<SQLProfileBucket_t> *pdictBuckets;
};
static int SQLProfileSortFunc( void *pCtx, const int *lhs, const int *rhs );
#endif
#ifdef DEBUG_JOB_LIST
// static job debug list
static CUtlLinkedList<CJob *, int> sm_listAllJobs;
#endif
bool m_bDebugDisallowPause;
};
//-----------------------------------------------------------------------------
// Purpose: passthrough function just so the CJob internal data can be kept private
//-----------------------------------------------------------------------------
inline void Job_RegisterJobType( const JobType_t *pJobType )
{
CJobMgr::RegisterJobType( pJobType );
}
//-----------------------------------------------------------------------------
// Purpose: passthrough function just so the CJob internal data can be kept private
//-----------------------------------------------------------------------------
inline void Job_SetJobType( CJob &job, const JobType_t *pJobType )
{
job.m_pJobType = pJobType;
}
//-----------------------------------------------------------------------------
// Purpose: job registration macro
//-----------------------------------------------------------------------------
#define GC_REG_JOB( parentclass, jobclass, jobname, msg, servertype ) \
GCSDK::CJob *CreateJob_##jobclass( parentclass *pParent, void * pvStartParam ); \
static const GCSDK::JobType_t g_JobType_##jobclass = { jobname, (GCSDK::MsgType_t)msg, servertype, (GCSDK::JobCreationFunc_t)CreateJob_##jobclass }; \
GCSDK::CJob *CreateJob_##jobclass( parentclass *pParent, void * pvStartParam ) \
{ \
GCSDK::CJob *job = GCSDK::CJob::AllocateJob<jobclass>( pParent ); \
if ( job ) \
{ \
Job_SetJobType( *job, &g_JobType_##jobclass ); \
if ( pvStartParam ) job->SetStartParam( pvStartParam ); \
} \
else \
{ \
AssertMsg( job, "CJob::AllocateJob<" #jobclass "> returned NULL!" ); \
} \
return job; \
} \
static class CRegJob_##jobclass \
{ \
public: CRegJob_##jobclass() \
{ \
Job_RegisterJobType( &g_JobType_##jobclass ); \
} \
} g_RegJob_##jobclass;
//-----------------------------------------------------------------------------
// Purpose: job registration macro for job triggered by web api request
//-----------------------------------------------------------------------------
#define REG_WEBAPI_JOB( parentclass, jobclass, jobname, servertype ) \
CJob *CreateJob_##jobclass( parentclass *pParent, void * pvStartParam ); \
static const JobType_t g_JobType_##jobclass = { jobname, k_EGCMsgInvalid, servertype, (JobCreationFunc_t)CreateJob_##jobclass }; \
CJob *CreateJob_##jobclass( parentclass *pParent, void * pvStartParam ) \
{ \
CJob *job = CJob::AllocateJob<jobclass>( pParent ); \
if ( job ) \
{ \
Job_SetJobType( *job, &g_JobType_##jobclass ); \
if ( pvStartParam ) job->SetStartParam( pvStartParam ); \
} \
else \
{ \
AssertMsg( job, "CJob::AllocateJob<" #jobclass "> returned NULL!" ); \
} \
return job; \
} \
static class CRegJob_##jobclass \
{ \
public: CRegJob_##jobclass() \
{ \
Job_RegisterJobType( &g_JobType_##jobclass ); \
} \
} g_RegJob_##jobclass;
} // namespace GCSDK
#include "tier0/memdbgoff.h"
#endif // GC_JOBMGR_H
+60
View File
@@ -0,0 +1,60 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Encapsultes the job system's version of time (which is != to wall clock time)
//
//=============================================================================
#ifndef JOBTIME_H
#define JOBTIME_H
#ifdef _WIN32
#pragma once
#endif
namespace GCSDK
{
// CJobTime
// This is our primary job time structure. It's similar to the Windows FILETIME structure, but
// with 1/10th the nominal resolution.
// It offers 1 microsecond resolution beginning on January 1, 1601.
// This is NOT wall clock time, it is time based proxy for a frame counter.
// This time PAUSES when you are in the debugger. Most timeout checks (things that don't need to be
// exact time wise should use this );
class CJobTime
{
public:
CJobTime();
void SetToJobTime();
void SetFromJobTime( int64 dMicroSecOffset );
// Amount of time that's passed between this CJobTime and the current time.
int64 CServerMicroSecsPassed() const;
// Time accessors
uint64 LTime() const { return m_lTime; }
void SetLTime( uint64 lTime ) { m_lTime = lTime; }
// Access our cached current time value
static void UpdateJobTime( int cMicroSecPerShellFrame );
static void SetCurrentJobTime( uint64 lCurrentTime );
static uint64 LJobTimeCur() { return sm_lTimeCur; }
bool operator==( const CJobTime &val ) const { return val.m_lTime == m_lTime; }
bool operator!=( const CJobTime &val ) const { return val.m_lTime != m_lTime; }
bool operator<( const CJobTime &val ) const { return m_lTime < val.m_lTime; }
bool operator>( const CJobTime &val ) const { return m_lTime > val.m_lTime; }
const CJobTime& operator+( const int64 &val ) { m_lTime += val; return *this; }
const CJobTime& operator+=( const int64 &val ) { m_lTime += val; return *this; }
private:
uint64 m_lTime; // Our time value (microseconds since 1/1/1601)
static uint64 sm_lTimeCur; // Cached value of the current time (updated each frame)
};
const uint64 k_lJobTimeMaxFuture = (uint64)-1;
}
#endif // JOBTIME_H
+174
View File
@@ -0,0 +1,174 @@
//====== Copyright ©, Valve Corporation, All rights reserved. =======
//
// Purpose: Maps message types to strings and vice versa
//
//=============================================================================
#ifndef MESSAGELIST_H
#define MESSAGELIST_H
#ifdef _WIN32
#pragma once
#endif
// Protobuf headers interfere with the valve min/max/malloc overrides. so we need to do all
// this funky wrapping to make the include happy.
#include <tier0/valve_minmax_off.h>
#include "google/protobuf/descriptor.h"
#include <tier0/valve_minmax_on.h>
#include "gcsdk/jobtime.h"
namespace GCSDK
{
extern CGCEmitGroup g_EGMessages;
extern const char *PchMsgNameFromEMsg( MsgType_t eMsg );
//-----------------------------------------------------------------------------
// message type flags
//-----------------------------------------------------------------------------
static const int MT_GC = 0x01; // this message is sent to or from a Game Coordinator (will be proxied by servers along the way)
static const int MT_GC_SYSTEM = 0x02; // this message was sent to or from the steam servers as a system message. Clients can't send these
//-----------------------------------------------------------------------------
// Various info about each message type
//-----------------------------------------------------------------------------
struct MsgInfo_t
{
MsgType_t eMsg;
int nFlags;
const char *pchMsgName;
struct Stats_t
{
Stats_t() : nSourceMask(0), nCount( 0 ), uBytes( 0 ) {}
uint32 nSourceMask;
uint32 nCount;
uint64 uBytes;
};
enum EStatsType
{
k_EStatsTypeSent,
k_EStatsTypeReceived,
k_EStatsTypeMultiplexedSends,
k_EStatsTypeMultiplexedSendsRaw,
k_EStatsType_Count
};
enum EStatsGroup
{
k_EStatsGroupGlobal,
k_EStatsGroupProfile,
k_EStatsGroupWindow,
k_EStatsGroup_Count
};
Stats_t stats[ k_EStatsGroup_Count ][ k_EStatsType_Count ];
};
//-----------------------------------------------------------------------------
// Purpose: Using protobuf reflection, bind them into a message list
//-----------------------------------------------------------------------------
void MsgRegistrationFromEnumDescriptor( const ::google::protobuf::EnumDescriptor *pEnumDescriptor, int nTypeMask );
//-----------------------------------------------------------------------------
// manages a hashed list of messages, allowing fast tests for validity and
// info lookup.
//-----------------------------------------------------------------------------
class CMessageList
{
public:
CMessageList();
~CMessageList();
bool BInit( );
// returns false if a message isn't valid or isn't one of the types specified
// or true if the message is valid. ppMsgName can be NULL.
bool GetMessage( MsgType_t eMsg, const char **ppMsgName, int nTypeMask );
// make stats about sending messages
void TallySendMessage( MsgType_t eMsg, uint32 uMsgSize, uint32 nSourceMask = 0 );
void TallyReceiveMessage( MsgType_t eMsg, uint32 uMsgSize, uint32 nSourceMask = 0);
void TallyMultiplexedMessage( MsgType_t eMsg, uint32 uSent, uint32 cRecipients, uint32 uMsgSize, uint32 nSourceMask = 0 );
// profiling
void EnableProfiling( bool bEnableProfiling );
// print out our stats
void PrintStats( bool bShowAll, bool bSortByFrequency, MsgInfo_t::EStatsGroup eGroup, MsgInfo_t::EStatsType eType, uint32 nSourceMask = 0 ) const;
void PrintMultiplexStats( MsgInfo_t::EStatsGroup eGroup, bool bSortByFrequency, uint32 nSourceMask = 0 ) const;
// Window management - This is similar to profiling in many ways, but is separate so that the base system can monitor traffic rates without
// interfering with profiles.
//called to obtain the totals for the timing window
const MsgInfo_t::Stats_t& GetWindowTotal( MsgInfo_t::EStatsType eType ) const;
//called to reset the window timings
void ResetWindow();
//returns how long this window has been running in microseconds
uint64 GetWindowDuration() const { return GetGroupDuration( MsgInfo_t::k_EStatsGroupWindow ); }
private:
void TallyMessageInternal( MsgInfo_t &msgInfo, MsgInfo_t::EStatsType eBucket, uint32 unMsgSize, uint32 nSourceMask, uint32 cMessages = 1 );
void AssureBucket( int nBucket );
//-----------------------------------------------------------------------------
// given a particular message ID, find out what bucket it would be in,
// as well as which slot in that bucket.
//-----------------------------------------------------------------------------
static int HashMessage( MsgType_t eMsg, int &nSlot )
{
// hash is everything except the lowest nibble,
// because buckets are 16 entries
int nBucket = eMsg / m_kcBucketSize;
nSlot = eMsg % m_kcBucketSize;
return nBucket;
}
short GetMessageIndex( MsgType_t eMsg );
//given a group, this will return the time that the stats have been collected over
uint64 GetGroupDuration( MsgInfo_t::EStatsGroup eGroup ) const;
private:
//totalled stats for the current window. It would be too costly to total these all the time
MsgInfo_t::Stats_t m_WindowTotals[ MsgInfo_t::k_EStatsType_Count ];
//the time that we have been collecting each of these buckets
CJobTime m_sCollectTime[ MsgInfo_t::k_EStatsGroup_Count ];
//are we currently actively tracking a profile?
bool m_bProfiling;
//the duration of the last finished profile (if it is no longer running, otherwise use the timer)
uint64 m_ulProfileMicrosecs;
CUtlVector< short* > m_vecMessageInfoBuckets;
CUtlVector<MsgInfo_t> m_vecMsgInfo;
static const int m_kcBucketSize = 16;
};
extern CMessageList g_theMessageList;
//-----------------------------------------------------------------------------
// Purpose: Returns the true if the specified message is a valid GC system message.
// Input : eMsg - message type to test
// ppMsgName - Optional pointer to receive message name
//-----------------------------------------------------------------------------
inline bool BIsValidSystemMsg( MsgType_t eMsg, const char **ppMsgName )
{
return g_theMessageList.GetMessage( eMsg, ppMsgName, MT_GC_SYSTEM );
}
} // namespace GCSDK
#endif // MESSAGELIST_H
+870
View File
@@ -0,0 +1,870 @@
//====== Copyright ©, Valve Corporation, All rights reserved. =======
//
// Purpose: Holds the CMsgBase_t class
//
//=============================================================================
#ifndef GCMSGBASE_H
#define GCMSGBASE_H
#ifdef _WIN32
#pragma once
#endif
#include "tier1/tsmultimempool.h"
#include "gclogger.h"
#include "gcconstants.h"
#include "refcount.h"
namespace GCSDK
{
class CNetPacket;
// used for message types in GCSDK where we don't have the actual enum
typedef uint32 MsgType_t;
const uint32 k_EMsgProtoBufFlag = 0x80000000;
//extern ConVar g_ConVarMsgErrorDump;
extern CThreadSafeMultiMemoryPool g_MemPoolMsg;
enum EMsgFormatType
{
k_EMsgFormatTypeStruct = 0,
k_EMsgFormatTypeClientStruct = 1,
k_EMsgFormatTypeClientStructDeprecated = 2,
k_EMsgFormatTypeProtocolBuffer = 3
};
//
// Interface that the CNetPacket wrappers for both old/new inter-server
// message formats must implement.
//
class IMsgNetPacket : public CRefCount
{
public:
virtual EMsgFormatType GetEMsgFormatType() const = 0;
virtual CNetPacket *GetCNetPacket() const = 0;
virtual uint8 *PubData() const = 0;
virtual uint CubData() const = 0;
//
// Inter-server or client messages in both old/new formats
//
virtual MsgType_t GetEMsg() const = 0;
virtual JobID_t GetSourceJobID() const = 0;
virtual JobID_t GetTargetJobID() const = 0;
virtual void SetTargetJobID( JobID_t ulJobID ) = 0;
//
// Client messages only in the old format, optional in any msg in the new format
//
virtual CSteamID GetSteamID() const = 0;
virtual void SetSteamID( CSteamID steamID ) = 0;
// Inter-gc messages only
virtual AppId_t GetSourceAppID() const = 0;
virtual void SetSourceAppID( AppId_t appId ) = 0;
//
// The name of the job type to route this message to. ProtoBuf messages only
//
virtual bool BHasTargetJobName() const = 0;
virtual const char *GetTargetJobName() const = 0;
protected:
// Needed due to CRefCount inheritance which makes this not a pure interface class
virtual ~IMsgNetPacket() {}
};
IMsgNetPacket *IMsgNetPacketFromCNetPacket( CNetPacket *pNetPacket );
// Wrapper around IMsgNetPacket which auto-releases
class CIMsgNetPacketAutoRelease
{
public:
CIMsgNetPacketAutoRelease( CNetPacket *pNetPacket ) { m_pMsgNetPacket = IMsgNetPacketFromCNetPacket( pNetPacket ); }
~CIMsgNetPacketAutoRelease() { SAFE_RELEASE( m_pMsgNetPacket ); }
void Replace( CNetPacket *pNetPacket, bool bIsClientMsg ) { SAFE_RELEASE( m_pMsgNetPacket ); m_pMsgNetPacket = IMsgNetPacketFromCNetPacket( pNetPacket ); }
IMsgNetPacket *Get() { return m_pMsgNetPacket; }
IMsgNetPacket *operator->() { return m_pMsgNetPacket; }
protected:
void operator=( const CIMsgNetPacketAutoRelease &that ) { AssertMsg( false, "Not safe to copy since releases references on destruction" ); }
CIMsgNetPacketAutoRelease( const CIMsgNetPacketAutoRelease &that ) { AssertMsg( false, "Not safe to copy since releases references on destruction" ); }
IMsgNetPacket *m_pMsgNetPacket;
};
//-----------------------------------------------------------------------------
// Purpose: Helper class for incoming and outgoing network packets.
// IMPORTANT: Note the distinction between pubData and cubData
// (which refer to the message payload), and pubMsg and cubMsg
// (which refer to the entire message, with the header).
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
class CMsgBase_t
{
public:
// Send constructor
CMsgBase_t( uint32 cubStruct, uint32 cubReserve = 0 );
// copies data from pubPkt
CMsgBase_t( const uint8 *pubPkt, uint32 cubPkt );
// Receive constructor - aliases pubPkt
CMsgBase_t( uint32 cubHdr, uint32 cubStruct, uint8 *pubPkt, uint32 cubPkt/*, HCONNECTION hConnection = NULL*/ );
// set packet after using empty constructor
void SetPacket( IMsgNetPacket *pNetPacket );
// Destructor
virtual ~CMsgBase_t();
// Accessors
uint8 *PubVarData() { return ( m_pubPkt + m_cubMsgHdr + m_cubStruct ); }
const uint8 *PubVarData() const { return ( m_pubPkt + m_cubMsgHdr + m_cubStruct ); }
uint32 CubVarData() const
{
if ( m_cubPkt >= ( m_cubMsgHdr + m_cubStruct ) )
return m_cubPkt - m_cubMsgHdr - m_cubStruct;
else
return 0;
}
uint8 *PubPkt() { return m_pubPkt; }
const uint8 *PubPkt() const { return m_pubPkt; }
uint32 CubPkt() const { return m_cubPkt; }
MSG_HEADER_TYPE &Hdr() { return * ( MSG_HEADER_TYPE * ) ( m_pubPkt ); }
const MSG_HEADER_TYPE &Hdr() const { return * ( MSG_HEADER_TYPE * ) ( m_pubPkt ); }
uint32 CubHdr() const { return m_cubMsgHdr; }
uint8* PubBody() { return m_pubBody; }
const uint8* PubBody() const { return m_pubBody; }
uint32 CubBody() const { return CubPkt() - CubHdr(); }
// Add additional data
int DubWriteCur() { return m_cubPkt - m_cubMsgHdr - m_cubStruct; } // Our current offset within the var data block
void AddBoolData( bool bData );
void AddUint8Data( uint8 ubData );
void AddUintData( uint32 unData );
void AddIntData( int32 nData );
void AddInt16Data( int16 sData );
void AddUint16Data( uint16 usData );
void AddUint64Data( uint64 ulData );
void AddInt64Data( int64 lData );
void AddFloatData( float lData );
void AddVariableLenData( const void *pvData, uint cubLen );
void AddStrData( const char *pchIn );
template<typename T> void AddStructure(T& structure ) ;
// Read variable-length data (can also read manually using PubVarData(), CubVarData()
void ResetReadPtr() { m_pubVarRead = PubVarData(); }
uint8 *PubReadCur() { return m_pubVarRead; }
const uint8 *PubReadCur() const { return m_pubVarRead; }
uint32 CubReadRemaining() const { return (uint32)(m_pubPkt + m_cubPkt - m_pubVarRead); }
void AdvanceReadPtr( int cubSkip ) { m_pubVarRead += cubSkip; }
bool BReadBoolData( bool *pbData );
bool BReadUint8Data( uint8 *pbData );
bool BReadUintData( uint32 *punData );
bool BReadIntData( int32 *pnData );
bool BReadInt16Data( int16 *psData );
bool BReadUint16Data( uint16 *pusData );
bool BReadUint64Data( uint64 *pulData );
bool BReadInt64Data( int64 *plData );
bool BReadFloatData( float *pflData );
bool BReadVariableLenData( void *pvBuff, uint32 cubRead );
bool BReadStr( char *pchBuff, int cchBuff );
bool BReadStr( CUtlString *pstr );
template<typename T> bool BReadStructure(T& structure) ;
// returns pointer to data (and size of data in out ptr), and NULLs out own pointer;
// caller now owns this memory and must free
uint8 * DetachPkt( int * pcubPkt )
{
Assert( pcubPkt );
*pcubPkt = m_cubPkt;
uint8 * pRetVal = m_pubPkt;
m_pubPkt = NULL;
m_pubBody = NULL;
m_cubPkt = 0;
return pRetVal;
}
void ResetWritePtr()
{
m_cubPkt = m_cubMsgHdr + m_cubStruct;
}
uint32 GetWriteOffset() const { return m_cubPkt; }
void SetWriteOffset( uint32 nWriteOffset ) { m_cubPkt = nWriteOffset; }
// Called to set the JobID that will be expecting
// a reply to this message.
void ExpectingReply( JobID_t jobIDSource )
{
Hdr().m_JobIDSource = jobIDSource;
}
bool BIsExpectingReply() const { return Hdr().m_JobIDSource != k_GIDNil; }
// make sure the buffer can hold this extra amount of data
void EnsurePacketSize( uint32 cubNewsize );
//HCONNECTION GetHConnection() const { return m_hConnection; }
void ReportBufferOverflow();
void PacketDump(); // spews complete packet content to console
protected:
// Shared by send & receive
uint8 *m_pubPkt; // Raw packet data
uint8 *m_pubBody; // pointer to body; always equal to m_pubPkt + m_cubMsgHdr
uint32 m_cubPkt; // Raw packet size
const uint32 m_cubMsgHdr; // Size of our message header
uint32 m_cubStruct; // Size of our message-specific struct
//HCONNECTION m_hConnection; // Connection on which we received the message
private:
// stop people from hurting themselves
CMsgBase_t( const CMsgBase_t &rhs ) {};
CMsgBase_t &operator=( const CMsgBase_t &rhs ) {};
bool m_bAlloced; // Did we allocate this buffer or does someone else own it
// Receive only
uint8 *m_pubVarRead; // Our current read pointer in the variable-length data
};
template <typename MSG_HEADER_TYPE>
CMsgBase_t<MSG_HEADER_TYPE>::CMsgBase_t( uint32 cubStruct, uint32 cubReserve )
: m_cubMsgHdr( sizeof( MSG_HEADER_TYPE ) )
{
m_cubStruct = cubStruct;
// Alloc a buffer
m_cubPkt = m_cubMsgHdr + m_cubStruct;
m_pubPkt = (uint8 *) g_MemPoolMsg.Alloc( m_cubPkt + cubReserve );
m_pubBody = m_pubPkt + m_cubMsgHdr;
memset(m_pubPkt, 0, m_cubPkt );
m_bAlloced = true;
m_pubVarRead = NULL;
}
template <typename MSG_HEADER_TYPE>
CMsgBase_t<MSG_HEADER_TYPE>::CMsgBase_t( const uint8 *pubPkt, uint32 cubPkt )
: m_cubMsgHdr( 0 )
{
m_cubStruct = 0;
// Alloc a buffer
m_cubPkt = cubPkt;
m_pubPkt = (uint8 *) g_MemPoolMsg.Alloc( m_cubPkt );
m_pubBody = m_pubPkt + m_cubMsgHdr;
Q_memcpy(m_pubPkt, pubPkt, cubPkt );
m_bAlloced = true;
m_pubVarRead = NULL;
}
template <typename MSG_HEADER_TYPE>
CMsgBase_t<MSG_HEADER_TYPE>::CMsgBase_t( uint32 cubHdr, uint32 cubStruct, uint8 *pubPkt, uint32 cubPkt )
: m_cubMsgHdr( cubHdr )
{
Assert( cubHdr != 0 );
Assert( !cubPkt || ( cubPkt >= ( cubHdr + cubStruct ) ) );
m_cubStruct = cubStruct;
m_pubPkt = pubPkt;
m_pubBody = m_pubPkt + m_cubMsgHdr;
m_cubPkt = cubPkt;
m_bAlloced = false;
m_pubVarRead = PubVarData();
}
template <typename MSG_HEADER_TYPE>
CMsgBase_t<MSG_HEADER_TYPE>::~CMsgBase_t()
{
// if we allocated memory, free it
if ( m_bAlloced && m_pubPkt )
g_MemPoolMsg.Free( m_pubPkt );
}
//-----------------------------------------------------------------------------
// Purpose: ensure the packet can contain at least this much extra data
// Input: cubExtraSize - the amount of bytes to have room for
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
void CMsgBase_t<MSG_HEADER_TYPE>::SetPacket( IMsgNetPacket *pNetPacket )
{
m_pubPkt = pNetPacket->PubData();
m_pubBody = m_pubPkt + m_cubMsgHdr;
m_cubPkt = pNetPacket->CubData();
Assert( !m_cubPkt || ( m_cubPkt >= ( m_cubMsgHdr + m_cubStruct ) ) );
m_bAlloced = false;
m_pubVarRead = PubVarData();
}
//-----------------------------------------------------------------------------
// Purpose: ensure the packet can contain at least this much extra data
// Input: cubExtraSize - the amount of bytes to have room for
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
void CMsgBase_t<MSG_HEADER_TYPE>::EnsurePacketSize( uint32 cubExtraSize )
{
m_pubPkt = (uint8 *) g_MemPoolMsg.ReAlloc( m_pubPkt, m_cubPkt + cubExtraSize );
m_pubBody = m_pubPkt + m_cubMsgHdr;
}
//-----------------------------------------------------------------------------
// Purpose: Appends a bool to the variable length data associated with this message
// Input: bData - data to append
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
void CMsgBase_t<MSG_HEADER_TYPE>::AddBoolData( bool ubData )
{
return AddUint8Data( static_cast< uint8 >( ubData ) );
}
//-----------------------------------------------------------------------------
// Purpose: Appends a uint8 to the variable length data associated with this message
// Input: ubData - data to append
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
void CMsgBase_t<MSG_HEADER_TYPE>::AddUint8Data( uint8 ubData )
{
EnsurePacketSize( sizeof( uint8 ) );
*( ( uint8 * ) ( m_pubPkt + m_cubPkt ) ) = ubData;
m_cubPkt += sizeof( uint8 );
}
//-----------------------------------------------------------------------------
// Purpose: Appends a uint to the variable length data associated with this message
// Input: unData - data to append
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
void CMsgBase_t<MSG_HEADER_TYPE>::AddUintData( uint32 unData )
{
EnsurePacketSize( sizeof( uint32 ) );
*( ( uint32 * ) ( m_pubPkt + m_cubPkt ) ) = unData;
m_cubPkt += sizeof( uint32 );
}
//-----------------------------------------------------------------------------
// Purpose: Appends an int to the variable length data associated with this message
// Input: nData - data to append
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
void CMsgBase_t<MSG_HEADER_TYPE>::AddIntData( int32 nData )
{
EnsurePacketSize( sizeof( int32 ) );
*( ( int32 * ) ( m_pubPkt + m_cubPkt ) ) = nData;
m_cubPkt += sizeof( int32 );
}
//-----------------------------------------------------------------------------
// Purpose: Appends an int16 to the variable length data associated with this message
// Input: nData - data to append
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
void CMsgBase_t<MSG_HEADER_TYPE>::AddInt16Data( int16 sData )
{
EnsurePacketSize( sizeof( int16 ) );
*( ( int16 * ) ( m_pubPkt + m_cubPkt ) ) = sData;
m_cubPkt += sizeof( int16 );
}
//-----------------------------------------------------------------------------
// Purpose: Appends an int16 to the variable length data associated with this message
// Input: nData - data to append
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
void CMsgBase_t<MSG_HEADER_TYPE>::AddUint16Data( uint16 usData )
{
EnsurePacketSize( sizeof( uint16 ) );
*( ( uint16 * ) ( m_pubPkt + m_cubPkt ) ) = usData;
m_cubPkt += sizeof( uint16 );
}
//-----------------------------------------------------------------------------
// Purpose: Appends a uint64 to the variable length data associated with this message
// Input: ulData - data to append
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
void CMsgBase_t<MSG_HEADER_TYPE>::AddUint64Data( uint64 ulData )
{
EnsurePacketSize( sizeof( uint64 ) );
*( ( uint64 * ) ( m_pubPkt + m_cubPkt ) ) = ulData;
m_cubPkt += sizeof( uint64 );
}
//-----------------------------------------------------------------------------
// Purpose: Appends an int64 to the variable length data associated with this message
// Input: lData - data to append
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
void CMsgBase_t<MSG_HEADER_TYPE>::AddInt64Data( int64 lData )
{
EnsurePacketSize( sizeof( int64 ) );
*( ( int64 * ) ( m_pubPkt + m_cubPkt ) ) = lData;
m_cubPkt += sizeof( int64 );
}
//-----------------------------------------------------------------------------
// Purpose: Appends variable length data to this message. (Can be called
// repeatedly to append multiple data blocks.)
// Input: pvData - pointer to data to append
// cubData - size of data to append
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
void CMsgBase_t<MSG_HEADER_TYPE>::AddVariableLenData( const void *pvData, uint cubLen )
{
if ( cubLen > 0 )
{
EnsurePacketSize( cubLen );
memcpy( ( m_pubPkt + m_cubPkt ), pvData, cubLen );
m_cubPkt += cubLen;
}
}
//-----------------------------------------------------------------------------
// Purpose: Appends a float to the variable length data associated with this message
// Input: nData - data to append
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
void CMsgBase_t<MSG_HEADER_TYPE>::AddFloatData( float flData )
{
EnsurePacketSize( sizeof( float ) );
*( ( float * ) ( m_pubPkt + m_cubPkt ) ) = flData;
m_cubPkt += sizeof( float );
}
//-----------------------------------------------------------------------------
// Purpose: Appends a string to the variable-length portion of this message.
// Input: pchIn - String to append to the message
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
void CMsgBase_t<MSG_HEADER_TYPE>::AddStrData( const char *pchIn )
{
if ( !pchIn )
{
Assert( pchIn ); // passing a null string here is a code bug
return;
}
int cchIn = Q_strlen( pchIn );
EnsurePacketSize( cchIn + 1 );
Q_strncpy( ( char * ) ( m_pubPkt + m_cubPkt ), pchIn, cchIn + 1 );
m_cubPkt += ( cchIn + 1 );
}
template <typename MSG_HEADER_TYPE>
template <typename T>
void CMsgBase_t<MSG_HEADER_TYPE>::AddStructure( T& structure )
{
EnsurePacketSize(sizeof(structure)) ;
*( reinterpret_cast<T*>(m_pubPkt+m_cubPkt)) = structure ;
m_cubPkt += sizeof(structure) ;
}
//-----------------------------------------------------------------------------
// Purpose: Read a bool from the variable-length part of the message
// Input: pbData - [return] The value we read goes here
// Output: true if we were able to read, false if we overran the buffer
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
bool CMsgBase_t<MSG_HEADER_TYPE>::BReadBoolData( bool *pbData )
{
return BReadUint8Data( ( uint8* ) pbData );
}
//-----------------------------------------------------------------------------
// Purpose: Read a uint8 from the variable-length part of the message
// Input: pbData - [return] The value we read goes here
// Output: true if we were able to read, false if we overran the buffer
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
bool CMsgBase_t<MSG_HEADER_TYPE>::BReadUint8Data( uint8 *pbData )
{
if ( m_pubVarRead + sizeof( uint8 ) > m_pubPkt + m_cubPkt )
{
ReportBufferOverflow();
return false;
}
*pbData = * ( ( uint8 * ) m_pubVarRead );
m_pubVarRead += sizeof( uint8 );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Read a uint32 from the variable-length part of the message
// Input: punData - [return] The value we read goes here
// Output: true if we were able to read, false if we overran the buffer
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
bool CMsgBase_t<MSG_HEADER_TYPE>::BReadUintData( uint32 *punData )
{
if ( m_pubVarRead + sizeof( uint32 ) > m_pubPkt + m_cubPkt )
{
ReportBufferOverflow();
return false;
}
*punData = * ( ( uint32 * ) m_pubVarRead );
m_pubVarRead += sizeof( uint32 );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Reads an int32 from the variable-length part of the message
// Input: pnData - [return] The value we read goes here
// Output: true if we were able to read, false if we overran the buffer
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
bool CMsgBase_t<MSG_HEADER_TYPE>::BReadIntData( int32 *pnData )
{
if ( m_pubVarRead + sizeof( int32 ) > m_pubPkt + m_cubPkt )
{
ReportBufferOverflow();
return false;
}
*pnData = * ( ( int32 * ) m_pubVarRead );
m_pubVarRead += sizeof( int32 );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Reads an int16 from the variable-length part of the message
// Input: pnData - [return] The value we read goes here
// Output: true if we were able to read, false if we overran the buffer
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
bool CMsgBase_t<MSG_HEADER_TYPE>::BReadInt16Data( int16 *psData )
{
if ( m_pubVarRead + sizeof( int16 ) > m_pubPkt + m_cubPkt )
{
ReportBufferOverflow();
return false;
}
*psData = * ( ( int16 * ) m_pubVarRead );
m_pubVarRead += sizeof( int16 );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Reads an uint16 from the variable-length part of the message
// Input: pnData - [return] The value we read goes here
// Output: true if we were able to read, false if we overran the buffer
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
bool CMsgBase_t<MSG_HEADER_TYPE>::BReadUint16Data( uint16 *pusData )
{
if ( m_pubVarRead + sizeof( uint16 ) > m_pubPkt + m_cubPkt )
{
ReportBufferOverflow();
return false;
}
*pusData = * ( ( uint16 * ) m_pubVarRead );
m_pubVarRead += sizeof( uint16 );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Read a uint64 from the variable-length part of the message
// Input: pulData - [return] The value we read goes here
// Output: true if we were able to read, false if we overran the buffer
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
bool CMsgBase_t<MSG_HEADER_TYPE>::BReadUint64Data( uint64 *pulData )
{
if ( m_pubVarRead + sizeof( uint64 ) > m_pubPkt + m_cubPkt )
{
ReportBufferOverflow();
return false;
}
*pulData = * ( ( uint64 * ) m_pubVarRead );
m_pubVarRead += sizeof( uint64 );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Reads an int64 from the variable-length part of the message
// Input: plData - [return] The value we read goes here
// Output: true if we were able to read, false if we overran the buffer
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
bool CMsgBase_t<MSG_HEADER_TYPE>::BReadInt64Data( int64 *plData )
{
if ( m_pubVarRead + sizeof( int64 ) > m_pubPkt + m_cubPkt )
{
ReportBufferOverflow();
return false;
}
*plData = * ( ( int64 * ) m_pubVarRead );
m_pubVarRead += sizeof( int64 );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Reads a float from the variable-length part of the message
// Input: pflData - [return] The value we read goes here
// Output: true if we were able to read, false if we overran the buffer
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
bool CMsgBase_t<MSG_HEADER_TYPE>::BReadFloatData( float *pflData )
{
if ( m_pubVarRead + sizeof( float ) > m_pubPkt + m_cubPkt )
{
ReportBufferOverflow();
return false;
}
*pflData = * ( ( float * ) m_pubVarRead );
m_pubVarRead += sizeof( float );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Reads a block of data from the variable-length part of the message
// Input: pvBuff - [return] Buffer to copy the data into
// cubRead - Amount of data to read
// Output: true if we were able to read, false if we overran the buffer
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
bool CMsgBase_t<MSG_HEADER_TYPE>::BReadVariableLenData( void *pvBuff, uint32 cubRead )
{
if ( m_pubVarRead + cubRead > m_pubPkt + m_cubPkt )
{
ReportBufferOverflow();
return false;
}
Q_memcpy( pvBuff, m_pubVarRead, cubRead );
m_pubVarRead += cubRead;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Reads a string from the variable-length part of the message
// Input: pchBuff - [return] Buffer to copy the string into
// cchBuff - Size of the buffer
// Output: true if we were able to read, false if we overran the buffer
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
bool CMsgBase_t<MSG_HEADER_TYPE>::BReadStr( char *pchBuff, int cchBuff )
{
int cchRead = 0;
int cchLeft = CubReadRemaining(); // get bytes left in message
// search for string end in rest of message
while ( cchRead < cchLeft )
{
// if we hit the 0, stop
if ( *(m_pubVarRead+cchRead) == 0 )
break;
cchRead++;
}
cchRead++; // add the 0
// check if string fits into buffer and was found within packet bounds
if ( ( cchRead > cchBuff ) || (cchRead > cchLeft) )
{
// at least return an empty string since most code doesn't check the return value
if ( cchBuff > 0 )
pchBuff[0] = 0;
ReportBufferOverflow();
return false;
}
// copy the string to output buffer
Q_memcpy( pchBuff, m_pubVarRead, cchRead );
m_pubVarRead += ( cchRead * sizeof( char ) );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Reads a string from the variable-length part of the message
// Input: pchString - [return] copied string
// Output: true if we were able to read, false if we overran the buffer
//-----------------------------------------------------------------------------
template <typename MSG_HEADER_TYPE>
bool CMsgBase_t<MSG_HEADER_TYPE>::BReadStr( CUtlString *pstr )
{
if ( pstr == NULL )
return false;
int cchRead = 0;
int cchLeft = CubReadRemaining(); // get bytes left in message
// search for string end in rest of message
while ( cchRead < cchLeft )
{
// if we hit the 0, stop
if ( *(m_pubVarRead+cchRead) == 0 )
break;
cchRead++;
}
cchRead++; // add the 0
// check if string was found within packet bounds
if ( cchRead > cchLeft )
{
ReportBufferOverflow();
return false;
}
// copy the string
*pstr = (const char*)m_pubVarRead;
m_pubVarRead += ( cchRead * sizeof( char ) );
return true;
}
template <typename MSG_HEADER_TYPE>
template <typename T>
bool CMsgBase_t<MSG_HEADER_TYPE>::BReadStructure( T& structure )
{
int cbLeft = CubReadRemaining() ;
if( cbLeft >= sizeof(structure))
{
structure = *( reinterpret_cast<T*>(m_pubVarRead)) ;
m_pubVarRead += sizeof(structure) ;
return true ;
}
return false ;
}
template <typename MSG_HEADER_TYPE>
void CMsgBase_t<MSG_HEADER_TYPE>::PacketDump()
{
//if ( !g_ConVarMsgErrorDump.GetBool() )
// return;
EmitInfo( SPEW_NETWORK, SPEW_NEVER, LOG_ALWAYS, "Packet dump: raw size %u, header size %u, body size %u, var size %u\n", m_cubPkt, m_cubMsgHdr, m_cubStruct, CubVarData() );
EmitInfo( SPEW_NETWORK, SPEW_NEVER, LOG_ALWAYS, "Header dump: %s\n", Hdr().GetHeaderDescription().String() );
EmitInfo( SPEW_NETWORK, SPEW_NEVER, LOG_ALWAYS, "Struct dump: %u bytes\n", m_cubStruct );
char szLine[100] = "";
char szText[32] = "";
for ( uint i=0; i<m_cubStruct; i++ )
{
byte nValue = PubBody()[i];
uint nIndex = i%16;
Q_snprintf( szLine+3*nIndex, 8, "%02X ", nValue );
if ( nValue > 31 && nValue != '%' )
szText[nIndex] = nValue;
else
szText[nIndex] = '.';
if ( nIndex == 15 || i==(m_cubStruct-1))
{
szText[nIndex+1] = '\n';
szText[nIndex+2] = 0;
Q_strcat( szLine, "; ", sizeof(szLine) );
Q_strcat( szLine, szText, sizeof(szLine) );
EmitInfo( SPEW_NETWORK, SPEW_NEVER, LOG_ALWAYS, "%s", szLine );
szLine[0]=0;
}
}
uint cubVarData = MIN( CubVarData(), 1024u );
EmitInfo( SPEW_NETWORK, SPEW_NEVER, LOG_ALWAYS, "VarData dump: %u bytes\n", cubVarData );
for ( uint i=0; i<cubVarData; i++ )
{
byte nValue = PubVarData()[i];
uint nIndex = i%16;
Q_snprintf( szLine+3*nIndex, 8, "%02X ", nValue );
if ( nValue > 31 && nValue != '%' )
szText[nIndex] = nValue;
else
szText[nIndex] = '.';
if ( nIndex == 15 || i==(cubVarData-1))
{
szText[nIndex+1] = '\n';
szText[nIndex+2] = 0;
Q_strcat( szLine, " ; ", sizeof(szLine) );
Q_strcat( szLine, szText, sizeof(szLine) );
EmitInfo( SPEW_NETWORK, SPEW_NEVER, LOG_ALWAYS, "%s", szLine );
szLine[0]=0;
}
}
}
template<typename MSG_HEADER_TYPE>
void CMsgBase_t<MSG_HEADER_TYPE>::ReportBufferOverflow()
{
EmitWarning( SPEW_NETWORK, SPEW_ALWAYS, "Read buffer overflowed on incoming %s packet\n", Hdr().PchMsgName( ) );
PacketDump();
}
} // namespace GCSDK
#endif // GCMSGBASE_H
+473
View File
@@ -0,0 +1,473 @@
//====== Copyright © 1996-2004, Valve Corporation, All rights reserved. =======
//
// Purpose:
//
//=============================================================================
#ifndef MSGPROTOBUF_H
#define MSGPROTOBUF_H
#ifdef _WIN32
#pragma once
#endif
#include "msgbase.h"
#include "gcmsg.h"
#include "tier0/tslist.h"
// eliminates a conflict with TYPE_BOOL in OSX
#ifdef TYPE_BOOL
#undef TYPE_BOOL
#endif
#pragma warning(push)
#pragma warning( disable:4512 )
#include <tier0/valve_minmax_off.h>
#include "steammessages.pb.h"
#include <tier0/valve_minmax_on.h>
#pragma warning(pop)
namespace GCSDK
{
//-----------------------------------------------------------------------------
// CProtoBufNetPacket
// Thin wrapper around raw CNetPacket which implements our IMsgNetPacket interface.
//-----------------------------------------------------------------------------
class CProtoBufNetPacket : public IMsgNetPacket
{
#ifdef GC
DECLARE_CLASS_MEMPOOL( CProtoBufNetPacket );
#endif
public:
CProtoBufNetPacket( CNetPacket *pNetPacket, GCProtoBufMsgSrc eReplyType, const CSteamID steamID, uint32 nGCDirIndex, MsgType_t msgType );
//IMsgNetPacket
virtual EMsgFormatType GetEMsgFormatType() const OVERRIDE { return k_EMsgFormatTypeProtocolBuffer; }
virtual CNetPacket *GetCNetPacket() const OVERRIDE { return m_pNetPacket; }
virtual uint8 *PubData() const OVERRIDE { return m_pNetPacket->PubData(); }
virtual uint CubData() const OVERRIDE { return m_pNetPacket->CubData(); }
virtual MsgType_t GetEMsg() const OVERRIDE { return m_msgType; }
virtual JobID_t GetSourceJobID() const OVERRIDE { return m_pHeader->job_id_source(); }
virtual JobID_t GetTargetJobID() const OVERRIDE { return m_pHeader->job_id_target(); }
virtual void SetTargetJobID( JobID_t ulJobID ) OVERRIDE { m_pHeader->set_job_id_target( ulJobID ); }
virtual CSteamID GetSteamID() const OVERRIDE { return m_steamID; }
virtual void SetSteamID( CSteamID steamID ) OVERRIDE { m_steamID = steamID; }
virtual AppId_t GetSourceAppID() const OVERRIDE { return m_pHeader->source_app_id(); };
virtual void SetSourceAppID( AppId_t appId ) OVERRIDE { m_pHeader->set_source_app_id( appId ); }
virtual bool BHasTargetJobName() const OVERRIDE { return m_pHeader->has_target_job_name(); }
virtual const char *GetTargetJobName() const OVERRIDE { return m_pHeader->target_job_name().c_str(); }
bool IsValid() const { return m_bIsValid; }
ProtoBufMsgHeader_t &GetFixedHeader() const { return *( ( ProtoBufMsgHeader_t * )PubData() ); }
CMsgProtoBufHeader *GetProtoHeader() const { return m_pHeader; }
//called to obtain access to the body portion of the net packet associated with this message. Will return NULL if not in a valid state
bool GetMsgBody( const uint8*& pubData, uint32& cubData ) const;
private:
virtual ~CProtoBufNetPacket();
CNetPacket *m_pNetPacket;
CMsgProtoBufHeader *m_pHeader;
CSteamID m_steamID;
MsgType_t m_msgType;
bool m_bIsValid;
};
//-----------------------------------------------------------------------------
// CProtoBufMsgBase - Base class for templated protobuf msgs. As much code is
// in this class as possible to reduce template copy overhead
//-----------------------------------------------------------------------------
class CProtoBufMsgBase
{
public:
// allows any kind of destination to be the target of an AsyncSend
class IProtoBufSendHandler
{
public:
virtual bool BAsyncSend( MsgType_t eMsg, const uint8 *pubMsgBytes, uint32 cubSize ) = 0;
};
// Receive constructor. We expect InitFromPacket will be called later
CProtoBufMsgBase();
// Send constructor. InitFromPacket must not be called later
CProtoBufMsgBase( MsgType_t eMsgType );
virtual ~CProtoBufMsgBase();
bool InitFromPacket( IMsgNetPacket * pNetPacket );
bool BAsyncSend( IProtoBufSendHandler & pSender ) const;
bool BAsyncSendWithPreSerializedBody( IProtoBufSendHandler & pSender, const byte *pubBody, uint32 cubBody ) const;
//free standing version to send a protobuff given a header and pre-serialized body. Primarily used for efficient message routing
static bool BAsyncSendWithPreSerializedBody( IProtoBufSendHandler& sender, MsgType_t eMsgType, const CMsgProtoBufHeader& hdr, const byte* pubBody, uint32 cubBody );
//similar to the above, but sends a protobuf object that will be serialized into the buffer
static bool BAsyncSendProto( IProtoBufSendHandler& sender, MsgType_t eMsgType, const CMsgProtoBufHeader& hdr, const ::google::protobuf::Message& proto );
CMsgProtoBufHeader &Hdr() { return *m_pProtoBufHdr; }
const CMsgProtoBufHeader &Hdr() const { return *m_pProtoBufHdr; }
const CMsgProtoBufHeader &ConstHdr() const { return *m_pProtoBufHdr; }
MsgType_t GetEMsg() const { return m_eMsg & (~k_EMsgProtoBufFlag); }
CSteamID GetClientSteamID() const { return CSteamID( m_pProtoBufHdr->client_steam_id() ); }
JobID_t GetJobIDTarget() const { return m_pProtoBufHdr->job_id_target(); }
JobID_t GetJobIDSource() const { return m_pProtoBufHdr->job_id_source(); }
AppId_t GetSourceAppID() const { return m_pProtoBufHdr->source_app_id(); }
bool BIsExpectingReply() const { return GetJobIDSource() != k_GIDNil; }
void SetJobIDSource( JobID_t jobId ) { m_pProtoBufHdr->set_job_id_source( jobId ); }
void SetJobIDTarget( JobID_t jobId ) { m_pProtoBufHdr->set_job_id_target( jobId ); }
void SetSourceAppID( AppId_t appId ) { m_pProtoBufHdr->set_source_app_id( appId ); }
void ExpectingReply( JobID_t jobId ) { SetJobIDSource( jobId ); }
EResult GetEResult() const { return (EResult)ConstHdr().eresult(); }
void SetEResult( EResult eResult ) { Hdr().set_eresult( eResult ); }
const char *GetErrorMessage() const { return ConstHdr().error_message().c_str(); }
void SetErrorMessage( const char *pchErrorMessage ) { Hdr().set_error_message( pchErrorMessage ); }
void AppendErrorMessage( const char *pchErrorMessage ) { Hdr().mutable_error_message()->append( pchErrorMessage ); }
// Must be implemented by subclasses. Returns the body. The templated subclasses have their
// own body accessor that returns the body as the specific type
virtual ::google::protobuf::Message *GetGenericBody() const = 0;
protected:
// Mutex to use when registering a new pool type
static CThreadMutex s_PoolRegMutex;
private:
//utility function that handles allocating a memory pool big enough for the provided header and specified body
//size and writing the header into the pool. This will return a pointer to the memory, as well as the header size.
static uint8* AllocateMessageMemory( MsgType_t eMsgType, const CMsgProtoBufHeader& hdr, uint32 cubBodySize, uint32* pCubTotalSizeOut );
//called to free the memory returned by allocate message memory
static void FreeMessageMemory( uint8* pMemory );
// Pointer to an external net packet if we have one. If we have one then we will
// not have allocated m_pProtoBufHdr ourselves
CProtoBufNetPacket *m_pNetPacket;
// Protobuf objects for extended pb based header
CMsgProtoBufHeader *m_pProtoBufHdr;
// Our message type
MsgType_t m_eMsg;
// Private and unimplemented. Implement these if you want to be able to copy
// these objects.
CProtoBufMsgBase( const CProtoBufMsgBase& );
CProtoBufMsgBase& operator=( const CProtoBufMsgBase& );
};
//-----------------------------------------------------------------------------
// CProtoBufMsgMemoryPoolBase - Interface to allocation pools for each protobufmsg type
//-----------------------------------------------------------------------------
class CProtoBufMsgMemoryPoolBase
{
public:
CProtoBufMsgMemoryPoolBase( uint32 unTargetLow, uint32 unTargetHigh );
virtual ~CProtoBufMsgMemoryPoolBase();
// Memory interface
::google::protobuf::Message *Alloc();
void Free( ::google::protobuf::Message *pMsg );
// Stats
uint32 GetEstimatedSize();
uint32 GetAllocated() { return m_unAllocated; }
uint32 GetFree() { return m_pTSQueueFreeObjects->Count(); }
uint32 GetAllocHitCount() { return m_unAllocHitCounter; }
uint32 GetAllocMissCount() { return m_unAllocMissCounter; }
// To be overriden by the templated class
virtual CUtlString GetName() = 0;
protected:
// The actual memory management. Must be overriden by the templated class
virtual google::protobuf::Message *InternalAlloc() = 0;
virtual void InternalFree( google::protobuf::Message *pMsg ) = 0;
// Called by the derived destructor to deallocate the outstanding messages
bool PopItem( google::protobuf::Message **ppMsg );
private:
CTSQueue<google::protobuf::Message *> *m_pTSQueueFreeObjects;
// These counters are important to get correct, so interlocked in case of allocating on threads
CInterlockedInt m_unAllocHitCounter;
CInterlockedInt m_unAllocMissCounter;
CInterlockedInt m_unAllocated;
// Only set at construction, so not needed to be thread safe
uint32 m_unTargetCountLow;
uint32 m_unTargetCountHigh;
};
} // namespace GCDSK
// The rest of the file needs memdbgon because the code in the templates do actual allocation
#include "tier0/memdbgon.h"
namespace GCSDK
{
//-----------------------------------------------------------------------------
// CProtoBufMsgMemoryPool - Implementation for allocation pools for protobufmsgs.
// We create one of these per protobuf msg type, created on first construction of
// an object of that type.
//-----------------------------------------------------------------------------
template< typename PB_OBJECT_TYPE >
class CProtoBufMsgMemoryPool : public CProtoBufMsgMemoryPoolBase
{
public:
CProtoBufMsgMemoryPool()
: CProtoBufMsgMemoryPoolBase( PB_OBJECT_TYPE::descriptor()->options().GetExtension( msgpool_soft_limit ),
PB_OBJECT_TYPE::descriptor()->options().GetExtension( msgpool_hard_limit ) ) {}
virtual ~CProtoBufMsgMemoryPool()
{
google::protobuf::Message *pObject = NULL;
while ( PopItem( &pObject ) )
{
InternalFree( pObject );
}
}
virtual CUtlString GetName() OVERRIDE
{
return PB_OBJECT_TYPE::default_instance().GetTypeName().c_str();
}
private:
virtual ::google::protobuf::Message *InternalAlloc()
{
PB_OBJECT_TYPE *pObject = (PB_OBJECT_TYPE *)malloc( sizeof( PB_OBJECT_TYPE ) );
Construct( pObject );
return pObject;
}
virtual void InternalFree( google::protobuf::Message *pMsg )
{
if ( NULL == pMsg )
{
Assert( NULL != pMsg );
return;
}
PB_OBJECT_TYPE *pObject = (PB_OBJECT_TYPE *)pMsg;
Destruct( pObject );
FreePv( pObject );
}
};
//-----------------------------------------------------------------------------
// CProtoBufMsgMemoryPoolMgr - Manages all the message pools for protobufmsgs.
// Should have one global singleton instance of this which tracks all the pools
// for individual message types.
//-----------------------------------------------------------------------------
class CProtoBufMsgMemoryPoolMgr
{
public:
CProtoBufMsgMemoryPoolMgr();
~CProtoBufMsgMemoryPoolMgr();
void RegisterPool( CProtoBufMsgMemoryPoolBase *pPool );
void DumpPoolInfo();
CMsgProtoBufHeader *AllocProtoBufHdr() { return (CMsgProtoBufHeader *)m_PoolHeaders.Alloc(); }
void FreeProtoBufHdr( CMsgProtoBufHeader *pObject ) { m_PoolHeaders.Free( pObject ); }
private:
CProtoBufMsgMemoryPool<CMsgProtoBufHeader> m_PoolHeaders;
CUtlVector< CProtoBufMsgMemoryPoolBase * > m_vecMsgPools;
};
extern CProtoBufMsgMemoryPoolMgr *GProtoBufMsgMemoryPoolMgr();
//-----------------------------------------------------------------------------
// CProtoBufPtrMsg
// Similar to a CProtoBufMsg, but the constructor simply takes in a pointer which is a
// pointer to the protobuf object that is being wrapped by a message. This memory is managed
// by the caller, this object does nothing to free the memory
//-----------------------------------------------------------------------------
class CProtoBufPtrMsg : public CProtoBufMsgBase
{
public:
CProtoBufPtrMsg( google::protobuf::Message *pProto ) : m_pProtoBufBody( pProto ) {}
private:
virtual google::protobuf::Message *GetGenericBody() const OVERRIDE { return m_pProtoBufBody; }
// Protobuf object for the message body
google::protobuf::Message *m_pProtoBufBody;
// Private and unimplemented. Implement these if you want to be able to copy
// these objects.
CProtoBufPtrMsg( const CProtoBufPtrMsg& );
CProtoBufPtrMsg& operator=( const CProtoBufPtrMsg& );
};
//-----------------------------------------------------------------------------
// CProtoBufMsg
// New style steam inter-server message class based on Google Protocol Buffers
// Handles a message with a header of type MsgHdr_t, a body of type T, and optional variable length data
//-----------------------------------------------------------------------------
template< typename PB_OBJECT_TYPE >
class CProtoBufMsg : public CProtoBufMsgBase
{
private:
static bool s_bRegisteredWithMemoryPoolMgr;
static CProtoBufMsgMemoryPool< PB_OBJECT_TYPE > *s_pMemoryPool;
public:
// Used to alloc a protobuf of this type from the pool. Can be used by functions
// working with protobufs that aren't messages to take advantage of pooling
static PB_OBJECT_TYPE *AllocProto()
{
// If we haven't done registration do so now
// Called on construction of each object of this type, but only does work
// once to setup memory pools for the class type.
if ( !s_bRegisteredWithMemoryPoolMgr )
{
// Get the lock and make sure we still haven't
s_PoolRegMutex.Lock();
if ( !s_bRegisteredWithMemoryPoolMgr )
{
s_pMemoryPool = new CProtoBufMsgMemoryPool< PB_OBJECT_TYPE >();
GProtoBufMsgMemoryPoolMgr()->RegisterPool( s_pMemoryPool );
s_bRegisteredWithMemoryPoolMgr = true;
}
s_PoolRegMutex.Unlock();
}
return static_cast<PB_OBJECT_TYPE *>( s_pMemoryPool->Alloc() );
}
// Frees a protobuf allocated with AllocProto()
static void FreeProto( PB_OBJECT_TYPE *pbObj )
{
s_pMemoryPool->Free( pbObj );
}
// Constructor for an empty message
CProtoBufMsg( MsgType_t eMsg )
: CProtoBufMsgBase( eMsg )
, m_pProtoBufBody( NULL )
{
VPROF_BUDGET( "CProtoBufMsg::CProtoBufMsg( MsgType_t )", VPROF_BUDGETGROUP_OTHER_NETWORKING );
m_pProtoBufBody = AllocProto();
}
// Constructor for an empty message responding to a client
CProtoBufMsg( MsgType_t eMsg, CSteamID steamIDClient, int32 nSessionIDClient )
: CProtoBufMsgBase( eMsg )
, m_pProtoBufBody( NULL )
{
VPROF_BUDGET( "CProtoBufMsg::CProtoBufMsg( MsgType_t, CSteamID, int32 )", VPROF_BUDGETGROUP_OTHER_NETWORKING );
m_pProtoBufBody = AllocProto();
Hdr()->set_client_steam_id( steamIDClient.ConvertToUint64() );
Hdr()->set_client_session_id( nSessionIDClient );
}
// Constructor from an incoming netpacket
CProtoBufMsg( IMsgNetPacket *pNetPacket )
: CProtoBufMsgBase()
, m_pProtoBufBody( NULL )
{
m_pProtoBufBody = AllocProto();
InitFromPacket( pNetPacket );
}
// constructor for use in catching replies or any other place where you have nothing to stuff in
// the message at construct time
CProtoBufMsg()
: CProtoBufMsgBase()
, m_pProtoBufBody( NULL )
{
m_pProtoBufBody = AllocProto();
}
// Constructor for replying to another protobuf message
CProtoBufMsg( MsgType_t eMsg, const CProtoBufMsgBase & msgReplyingTo )
: CProtoBufMsgBase( eMsg )
, m_pProtoBufBody( NULL )
{
VPROF_BUDGET( "CProtoBufMsg::CProtoBufMsg( EMsg, CProtoBufMsgMemoryPoolBase )", VPROF_BUDGETGROUP_OTHER_NETWORKING );
m_pProtoBufBody = AllocProto();
// set up the actual reply
SetJobIDTarget( msgReplyingTo.GetJobIDSource() );
}
// Destructor
virtual ~CProtoBufMsg()
{
if ( m_pProtoBufBody )
{
FreeProto( m_pProtoBufBody );
m_pProtoBufBody = NULL;
}
}
// Accessors
PB_OBJECT_TYPE &Body() { return *m_pProtoBufBody; }
const PB_OBJECT_TYPE &Body() const { return *m_pProtoBufBody; }
private:
virtual google::protobuf::Message *GetGenericBody() const OVERRIDE { return m_pProtoBufBody; }
// Protobuf object for the message body
PB_OBJECT_TYPE *m_pProtoBufBody;
// Private and unimplemented. Implement these if you want to be able to copy
// these objects.
CProtoBufMsg( const CProtoBufMsg& );
CProtoBufMsg& operator=( const CProtoBufMsg& );
};
// Statics
template< typename PB_OBJECT_TYPE > bool CProtoBufMsg< PB_OBJECT_TYPE>::s_bRegisteredWithMemoryPoolMgr = false;
template< typename PB_OBJECT_TYPE > CProtoBufMsgMemoryPool< PB_OBJECT_TYPE > *CProtoBufMsg< PB_OBJECT_TYPE>::s_pMemoryPool = NULL;
//-----------------------------------------------------------------------------
// Purpose: Wrapper class to handle alloc/free using the pool allocators
//-----------------------------------------------------------------------------
template <class TMsg>
class CProtoBufPoolObj
{
private:
TMsg *m_pMsg;
private: // Disallow copying/assignment
CProtoBufPoolObj( CProtoBufPoolObj const &x );
CProtoBufPoolObj & operator = ( CProtoBufPoolObj const &x );
public:
CProtoBufPoolObj() { m_pMsg = CProtoBufMsg<TMsg>::AllocProto(); }
~CProtoBufPoolObj() { CProtoBufMsg<TMsg>::FreeProto( m_pMsg ); }
operator TMsg & () { return *m_pMsg; }
};
} // namespace GCSDK
// memdbgon is only supposed to be on in cpp files, turn it off in case the next thing includes has a conflict with it
#include "tier0/memdbgoff.h"
#endif // MSGPROTOBUF_H
+48
View File
@@ -0,0 +1,48 @@
//====== Copyright (c), Valve Corporation, All rights reserved. =======
//
// Purpose: Holds the CNetPacket class
//
//=============================================================================
#ifndef GCNETPACKET_H
#define GCNETPACKET_H
namespace GCSDK
{
//-----------------------------------------------------------------------------
// Purpose: reference-counted received network packet
//-----------------------------------------------------------------------------
class CNetPacket
{
public:
CNetPacket();
~CNetPacket();
//called to allocate a buffer for the net packet of the specified size. This takes an optional pointer
//of which it will copy into the data if appropriate
void Init( uint32 cubData, const void* pCopyData = NULL );
//called when working with a net packet that you want to reference a separate buffer
void InitAdoptBuffer( uint32 cubData, uint8* pubData );
void OrphanBuffer();
// data
uint8 *PubData() const { return m_pubData; }
uint CubData() const { return m_cubData; }
// ownership
void AddRef();
void Release();
private:
int m_cRef; // reference count, deletes self when 0
uint m_cubData;
uint8 *m_pubData;
};
} // namespace GCSDK
#endif // GCNETPACKET_H
+42
View File
@@ -0,0 +1,42 @@
//====== Copyright (c), Valve Corporation, All rights reserved. =======
//
// Purpose:
//
//=============================================================================
#ifndef GCNETPACKETPOOL_H
#define GCNETPACKETPOOL_H
#include "netpacket.h"
namespace GCSDK
{
class CNetPacket;
extern int g_cNetPacket;
extern CThreadSafeMultiMemoryPool g_MemPoolMsg;
class CNetPacketPool
{
public:
#ifdef _SERVER
static int CMBPacketMemPool() { return ( CNetPacketPool::sm_MemPoolNetPacket.CubTotalSize() / k_nMegabyte ); }
static int CMBPacketMemPoolInUse() { return ( CNetPacketPool::sm_MemPoolNetPacket.CubSizeInUse() / k_nMegabyte ); }
#endif // _SERVER
static CNetPacket *AllocNetPacket() { g_cNetPacket++; return sm_MemPoolNetPacket.Alloc(); }
private:
friend class CNetPacket;
static CClassMemoryPool<CNetPacket> sm_MemPoolNetPacket;
};
} // namespace GCSDK
#endif // GCNETPACKETPOOL_H
+165
View File
@@ -0,0 +1,165 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Shared object based on a CBaseRecord subclass
//
//=============================================================================
#ifndef PROTOBUFSHAREDOBJECT_H
#define PROTOBUFSHAREDOBJECT_H
#ifdef _WIN32
#pragma once
#endif
#include "google/protobuf/descriptor.h"
#include "tier1/KeyValues.h"
#if defined( GC ) && defined( DEBUG )
#include "gcbase.h"
#endif
namespace google
{
namespace protobuf
{
class Message;
}
}
namespace GCSDK
{
//----------------------------------------------------------------------------
// Purpose: Base class for CProtoBufSharedObject. This is where all the actual
// code lives.
//----------------------------------------------------------------------------
class CProtoBufSharedObjectBase : public CSharedObject
{
public:
typedef CSharedObject BaseClass;
virtual bool BParseFromMessage( const CUtlBuffer & buffer ) OVERRIDE;
virtual bool BParseFromMessage( const std::string &buffer ) OVERRIDE;
virtual bool BUpdateFromNetwork( const CSharedObject & objUpdate ) OVERRIDE;
virtual bool BIsKeyLess( const CSharedObject & soRHS ) const ;
virtual void Copy( const CSharedObject & soRHS );
virtual void Dump() const OVERRIDE;
#ifdef DBGFLAG_VALIDATE
virtual void Validate( CValidator &validator, const char *pchName );
#endif
#ifdef GC
virtual bool BAddToMessage( CUtlBuffer & bufOutput ) const OVERRIDE;
virtual bool BAddToMessage( std::string *pBuffer ) const OVERRIDE;
virtual bool BAddDestroyToMessage( CUtlBuffer & bufDestroy ) const OVERRIDE;
virtual bool BAddDestroyToMessage( std::string *pBuffer ) const OVERRIDE;
virtual bool BParseFromMemcached( CUtlBuffer & buffer ) OVERRIDE;
virtual bool BAddToMemcached( CUtlBuffer & bufOutput ) const OVERRIDE;
static bool SerializeToBuffer( const ::google::protobuf::Message & msg, CUtlBuffer & bufOutput );
#endif //GC
// Static helpers
static void Dump( const ::google::protobuf::Message & msg );
static KeyValues *CreateKVFromProtoBuf( const ::google::protobuf::Message & msg );
static void RecursiveAddProtoBufToKV( KeyValues *pKVDest, const ::google::protobuf::Message & msg );
protected:
virtual ::google::protobuf::Message *GetPObject() = 0;
const ::google::protobuf::Message *GetPObject() const { return const_cast<CProtoBufSharedObjectBase *>(this)->GetPObject(); }
private:
#ifdef GC
static ::google::protobuf::Message *BuildDestroyToMessage( const ::google::protobuf::Message & msg );
#endif //GC
};
//----------------------------------------------------------------------------
// Purpose: Template for making a shared object that uses a specific protobuf
// message class for its wire protocol and in-memory representation.
//----------------------------------------------------------------------------
template< typename Message_t, int nTypeID, bool bPublicMutable = true >
class CProtoBufSharedObject : public CProtoBufSharedObjectBase
{
public:
~CProtoBufSharedObject()
{
#if defined( GC ) && defined( DEBUG )
// Ensure this SO is not in any cache, or we have an error. We must provide the type since it is a virutal function otherwise
Assert( !GGCBase()->IsSOCached( this, nTypeID ) );
#endif
}
virtual int GetTypeID() const { return nTypeID; }
// WHERE IS YOUR GOD NOW
template< typename T >
using Public_Message_t = typename std::enable_if< bPublicMutable && std::is_same< T, Message_t >::value, Message_t & >::type;
template< typename T >
using Protected_Message_t = typename std::enable_if< !bPublicMutable && std::is_same< T, Message_t >::value, Message_t & >::type;
template< typename T = Message_t >
Public_Message_t<T> Obj() { return m_msgObject; }
const Message_t & Obj() const { return m_msgObject; }
typedef Message_t SchObjectType_t;
const static int k_nTypeID = nTypeID;
protected:
template< typename T = Message_t >
Protected_Message_t<T> MutObj() { return m_msgObject; }
::google::protobuf::Message *GetPObject() { return &m_msgObject; }
private:
Message_t m_msgObject;
};
//----------------------------------------------------------------------------
// Purpose: Template for making a shared object that uses a specific protobuf
// message class for its wire protocol and in-memory representation.
//
// The wrapper version of this class wraps a message allocated and
// owned elsewhere. The user of this class is in charge of
// guaranteeing that lifetime.
//----------------------------------------------------------------------------
template< typename Message_t, int nTypeID >
class CProtoBufSharedObjectWrapper : public CProtoBufSharedObjectBase
{
public:
CProtoBufSharedObjectWrapper( Message_t *pMsgToWrap )
: m_pMsgObject( pMsgToWrap )
{}
~CProtoBufSharedObjectWrapper()
{
#if defined( GC ) && defined( DEBUG )
// Ensure this SO is not in any cache, or we have an error. We must provide the type since it is a virutal function otherwise
Assert( !GGCBase()->IsSOCached( this, nTypeID ) );
#endif
}
virtual int GetTypeID() const { return nTypeID; }
Message_t & Obj() { return *m_pMsgObject; }
const Message_t & Obj() const { return *m_pMsgObject; }
typedef Message_t SchObjectType_t;
public:
const static int k_nTypeID = nTypeID;
protected:
::google::protobuf::Message *GetPObject() { return m_pMsgObject; }
private:
Message_t *m_pMsgObject;
};
} // GCSDK namespace
#endif //PROTOBUFSHAREDOBJECT_H
+107
View File
@@ -0,0 +1,107 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// $NoKeywords: $
//=============================================================================
#ifndef GCREFCOUNT_H
#define GCREFCOUNT_H
#include "tier0/memdbgon.h"
namespace GCSDK
{
// Base class for ref counted classes. Derive from this to be refcounted. Note:
// you can no longer be deleted directly or declared on the stack. Make your
// derived class' destructor private to ensure you can't be deleted directly or declared
// on the stack.
// utility class
template< class T >
class CAutoPtr
{
T *m_pT;
public:
CAutoPtr()
{
m_pT = NULL;
}
~CAutoPtr()
{
delete m_pT;
}
T *reset( T *p )
{
delete m_pT;
m_pT = p;
return m_pT;
}
T *TakeOwnership()
{
T *p = m_pT;
m_pT = NULL;
return p;
}
T *release( )
{
T *pT = m_pT;
m_pT = NULL;
return pT;
}
T *operator->()
{
return m_pT;
}
operator T*()
{
return m_pT;
}
protected:
T *operator=(T*p)
{
AssertMsg( NULL == m_pT, "If this assert fires, you're leaking.\n" );
m_pT = p;
return m_pT;
}
};
class CRefCount
{
public:
CRefCount() { m_cRef = 1; } // we are born with a ref count of 1
// increment ref count
int AddRef() { return ThreadInterlockedIncrement( &m_cRef ); }
// delete ourselves when ref count reaches 0
int Release()
{
Assert( m_cRef > 0 );
int cRef = ThreadInterlockedDecrement( &m_cRef );
if ( 0 == cRef )
DestroyThis();
return cRef;
}
protected:
// Classes that derive from this should make their destructors private and virtual!
virtual ~CRefCount() { Assert( 0 == m_cRef ); }
virtual void DestroyThis() { delete this; } // derived classes may override this if they want to be part of a mem pool
volatile int32 m_cRef; // ref count of this object
};
#define SAFE_RELEASE( x ) if ( NULL != ( x ) ) { ( x )->Release(); x = NULL; }
} // namespace GCSDK
#include "tier0/memdbgoff.h"
#endif // GCREFCOUNT_H
+205
View File
@@ -0,0 +1,205 @@
//====== Copyright (c), Valve Corporation, All rights reserved. =======
//
// Purpose: Provides a scheduled function manager that will bucket events into
// time chunks and execute them as time elapses
//
//=============================================================================
#ifndef SCHEDULEDFUNCTION_H
#define SCHEDULEDFUNCTION_H
#ifdef _WIN32
#pragma once
#endif
namespace GCSDK
{
//interface for events that can be scheduled to run on the GC base
class IGCScheduledFunction
{
public:
IGCScheduledFunction() : m_nAbsScheduleBucket( knInvalidBucket ) {}
virtual ~IGCScheduledFunction();
//called in response to our event time elapsing
virtual void OnEvent() = 0;
bool BIsScheduled() const { return m_nAbsScheduleBucket != IGCScheduledFunction::knInvalidBucket; }
private:
//the absolute bucket that we were scheduled in (or invalid). Used to enable deregistering
friend class CScheduledFunctionMgr;
uint32 m_nAbsScheduleBucket;
uint32 m_nLLIndex;
static const uint32 knInvalidBucket = ( uint32 )-1;
};
//utility for scheduling a global function
class CGlobalScheduledFunction :
public IGCScheduledFunction
{
public:
CGlobalScheduledFunction();
typedef void ( *func_t )();
void ScheduleMS( func_t pfn, uint32 nDelayMS );
void ScheduleSecond( func_t pfn, uint32 nDelaySecond );
void ScheduleMinute( func_t pfn, uint32 nDelayMinute );
void Cancel();
virtual void OnEvent() OVERRIDE;
private:
func_t m_pfn;
};
//the same as the above, but automatically deletes the object once the event is fired
class CGlobalScheduledFunctionAutoDelete :
public CGlobalScheduledFunction
{
public:
CGlobalScheduledFunctionAutoDelete() {}
virtual void OnEvent() OVERRIDE
{
CGlobalScheduledFunction::OnEvent();
delete this;
}
};
//utility for scheduling a member function
template< class T >
class CScheduledFunction :
public IGCScheduledFunction
{
public:
CScheduledFunction() : m_pObj( NULL ), m_pfn( NULL ) {}
typedef void ( T::*func_t )();
void ScheduleMS( T* pObj, func_t pfn, uint32 nDelayMS )
{
m_pObj = pObj;
m_pfn = pfn;
GScheduledFunctionMgr().ScheduleMS( this, nDelayMS );
}
void ScheduleSecond( T* pObj, func_t pfn, uint32 nDelaySecond )
{
m_pObj = pObj;
m_pfn = pfn;
GScheduledFunctionMgr().ScheduleSecond( this, nDelaySecond );
}
void ScheduleMinute( T* pObj, func_t pfn, uint32 nDelayMinute )
{
m_pObj = pObj;
m_pfn = pfn;
GScheduledFunctionMgr().ScheduleMinute( this, nDelayMinute );
}
void Cancel()
{
GScheduledFunctionMgr().Cancel( this );
}
virtual void OnEvent() OVERRIDE
{
( m_pObj->*m_pfn )();
}
private:
T* m_pObj;
func_t m_pfn;
};
//similar to the above, but auto deletes once the event is fired
template< class T >
class CScheduledFunctionAutoDelete :
public CScheduledFunction< T >
{
public:
typedef void ( T::*func_t )();
CScheduledFunctionAutoDelete() {}
CScheduledFunctionAutoDelete( T* pObj, func_t pfn, uint32 nDelayMS ) { Schedule( pObj, pfn, nDelayMS ); }
virtual void OnEvent() OVERRIDE
{
CScheduledFunction< T >::OnEvent();
delete this;
}
};
class CScheduledFunctionMgr
{
public:
CScheduledFunctionMgr();
//called to initialize the starting time for the scheduled function manager. It doesn't need to be globally absolute, just relative to the time values provided
//to the run function
void InitStartingTime();
//called to register a scheduled event for a certain period in the future, with the delay specified in milliseconds. This has resolution of an individual frame, and
//will unregister from any previously registered time slot
void ScheduleMS( IGCScheduledFunction* pEvent, uint32 nMSDelay );
//similar to the above, but instead of having frame resolution, this has second level resolution, and should be used for low granularity tasks
void ScheduleSecond( IGCScheduledFunction* pEvent, uint32 nSDelay );
//similar to the above but has minute level resolution which should be used for very low resolution tasks
void ScheduleMinute( IGCScheduledFunction* pEvent, uint32 nMinuteDelay );
//deregisters a previously registered event
void Cancel( IGCScheduledFunction* pEvent );
//called to run registered functions
void RunFunctions();
private:
//called internally by the other schedule functions to schedule the event at the specified resolution in our resolution array
void InternalSchedule( uint32 nResolution, IGCScheduledFunction* pEvent, uint32 nMSDelay );
//the list type that we store all of the entries in. We use a single list to avoid the overhead of so many lists
typedef CUtlLinkedList< IGCScheduledFunction*, uint32 > TScheduleList;
//all information tied to a specific resolution, including the time hash buckets, number of buckets, and which buckets it has executed
class CScheduleBucket
{
public:
CScheduleBucket();
~CScheduleBucket();
void Init( TScheduleList& MasterList, uint32 nNumBuckets, uint32 nMicroSPerBucket );
//maps a micro second time to a bucket time
uint32 GetAbsScheduleBucketIndex( uint64 nMicroSTime ) const { return ( uint32 )( nMicroSTime / m_nMicroSPerBucket ); }
//called to run registered functions
void RunFunctions( TScheduleList& MasterList, uint64 nMicroSTime );
//for each bucket, we insert a node into the master linked list, then our list runs from this node to the next empty node (or end) in the list
uint32* m_pBuckets;
//the number of buckets that we have
uint32 m_nNumBuckets;
//how many micro seconds each bucket represents
uint32 m_nMicroSPerBucket;
//the last bucket that we had executed
uint32 m_nAbsLastScheduleBucket;
};
//the list of all of our entries. We store bucket starts within here, and then insert the events in between them
TScheduleList m_ScheduleList;
//the list of resolutions that we have
CScheduleBucket m_Resolutions[ 3 ];
};
//global singleton access
CScheduledFunctionMgr& GScheduledFunctionMgr();
} //namespace GCSDK
#endif
+127
View File
@@ -0,0 +1,127 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Shared object based on a CBaseRecord subclass
//
//=============================================================================
#ifndef SCHEMASHAREDOBJECT_H
#define SCHEMASHAREDOBJECT_H
#ifdef _WIN32
#pragma once
#endif
#ifndef GC
#error "CSchemaSharedObject is only intended for use on GC-only shared objects. It shouldn't be included on the client"
#endif
#if defined( DEBUG )
#include "gcbase.h"
#endif
namespace GCSDK
{
//----------------------------------------------------------------------------
// Purpose: Contains helper functions to deal with passed in CRecordInfo,
// so that a CRecordInfo can be built on the fly
//----------------------------------------------------------------------------
class CSchemaSharedObjectHelper
{
public:
static bool BYieldingAddInsertToTransaction( CSQLAccess & sqlAccess, CRecordBase *pRecordBase );
static bool BYieldingAddWriteToTransaction( CSQLAccess & sqlAccess, CRecordBase *pRecordBase, const CColumnSet & csDatabaseDirty );
static bool BYieldingAddRemoveToTransaction( CSQLAccess & sqlAccess, CRecordBase *pRecordBase );
static bool BYieldingAddToDatabase( CRecordBase *pRecordBase );
static bool BYieldingReadFromDatabase( CRecordBase *pRecordBase );
static void Dump( const CRecordBase *pRecordBase );
};
//----------------------------------------------------------------------------
// Purpose: Base class for CSchemaSharedObject. This is where all the actual
// code lives.
//----------------------------------------------------------------------------
class CSchemaSharedObjectBase : public CSharedObject
{
public:
typedef CSharedObject BaseClass;
CSchemaSharedObjectBase( CRecordInfo *pRecordInfo ) : BaseClass() {}
virtual bool BYieldingAddInsertToTransaction( CSQLAccess & sqlAccess );
virtual bool BYieldingAddWriteToTransaction( CSQLAccess & sqlAccess, const CUtlVector< int > &fields );
virtual bool BYieldingAddRemoveToTransaction( CSQLAccess & sqlAccess );
virtual bool BYieldingAddToDatabase();
virtual bool BYieldingReadFromDatabase() ;
// schema shared objects are GC-only and are never sent to clients.
virtual bool BIsNetworked() const { return false; }
virtual bool BParseFromMessage( const CUtlBuffer & buffer ) { return false; }
virtual bool BParseFromMessage( const std::string &buffer ) { return false; }
virtual bool BAddToMessage( CUtlBuffer & bufOutput ) const { return false; }
virtual bool BAddToMessage( std::string *pBuffer ) const { return false; }
virtual bool BAddDestroyToMessage( CUtlBuffer & bufDestroy ) const { return false; }
virtual bool BAddDestroyToMessage( std::string *pBuffer ) const { return false; }
virtual bool BUpdateFromNetwork( const CSharedObject & objUpdate ) { return false; }
virtual bool BIsKeyLess( const CSharedObject & soRHS ) const;
virtual void Copy( const CSharedObject & soRHS );
virtual void Dump() const;
// virtual bool BIsNetworkDirty() const { return false; }
// virtual void MakeNetworkClean() {}
const CRecordInfo * GetRecordInfo() const;
#ifdef DBGFLAG_VALIDATE
virtual void Validate( CValidator &validator, const char *pchName );
#endif
protected:
virtual CRecordBase *GetPObject() = 0;
const CRecordBase *GetPObject() const { return const_cast<CSchemaSharedObjectBase *>(this)->GetPObject(); }
CColumnSet GetDatabaseDirtyColumnSet( const CUtlVector< int > &fields ) const;
};
//----------------------------------------------------------------------------
// Purpose: Template for making a shared object that uses a specific Schema
// class for its data store.
//----------------------------------------------------------------------------
template< typename SchObject_t, int nTypeID >
class CSchemaSharedObject : public CSchemaSharedObjectBase
{
public:
CSchemaSharedObject()
: CSchemaSharedObjectBase( GCSDK::GSchemaFull().GetSchema( SchObject_t::k_iTable ).GetRecordInfo() )
{
}
~CSchemaSharedObject()
{
#ifdef DEBUG
// Ensure this SO is not in any cache, or we have an error. Note we must provide the type
//since we are in a destructor and it is unsafe to call virtual functions
Assert( !GGCBase()->IsSOCached( this, nTypeID ) );
#endif
}
virtual int GetTypeID() const { return nTypeID; }
SchObject_t & Obj() { return m_schObject; }
const SchObject_t & Obj() const { return m_schObject; }
typedef SchObject_t SchObjectType_t;
public:
const static int k_nTypeID = nTypeID;
const static int k_iFieldMax = SchObject_t::k_iFieldMax;
protected:
CRecordBase *GetPObject() { return &m_schObject; }
private:
SchObject_t m_schObject;
};
} // namespace GCSDK
#endif //SCHEMASHAREDOBJECT_H
File diff suppressed because it is too large Load Diff
+305
View File
@@ -0,0 +1,305 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Base class for objects that are kept in synch between client and server
//
//=============================================================================
#ifndef SHAREDOBJECT_H
#define SHAREDOBJECT_H
#ifdef _WIN32
#pragma once
#endif
// ENABLE_SO_OVERWRITE_PARANOIA can be set to either 0 or 1. If enabled, it will add
// extra fields to every CSharedObject instance to try and detect overwrites at the
// cost of additional runtime memory.
#define ENABLE_SO_OVERWRITE_PARANOIA 0
// ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA can be set to either 0 or 1. If enabled, it
// will add extra fields to every CSharedObject instance to try and detect issues with
// constructions/destruction (ie., double-deletes, etc.), including reference counting.
#define ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA (defined( STAGING_ONLY ))
#include "tier0/memdbgon.h"
namespace GCSDK
{
class CSQLAccess;
class CSharedObject;
typedef CSharedObject *(*SOCreationFunc_t)( );
class CSharedObjectCache;
//----------------------------------------------------------------------------
// Purpose: Abstract base class for objects that are shared between the GC and
// a gameserver/client. These can also be stored in the database.
//----------------------------------------------------------------------------
class CSharedObject
{
friend class CGCSharedObjectCache;
friend class CSharedObjectCache;
public:
#ifdef GC
virtual ~CSharedObject()
{
#if ENABLE_SO_OVERWRITE_PARANOIA
m_pThis = NULL;
#endif // ENABLE_SO_OVERWRITE_PARANOIA
#if ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
AssertMsg2( m_nRefCount == 0, "Deleting shared object type %d with refcount %d", m_nSOTypeID, m_nRefCount );
m_nSOTypeID = -1;
m_nRefCount = -1;
#endif // ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
}
#if ENABLE_SO_OVERWRITE_PARANOIA
CSharedObject *m_pThis;
#endif // ENABLE_SO_OVERWRITE_PARANOIA
#if ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
int m_nRefCount;
mutable int m_nSOTypeID;
#endif // ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
void Check() const
{
#if ENABLE_SO_OVERWRITE_PARANOIA
Assert( m_pThis == this );
#endif // ENABLE_SO_OVERWRITE_PARANOIA
#if ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
Assert( m_nRefCount >= 0 );
if ( m_nSOTypeID == kSharedObject_UnassignedType )
{
m_nSOTypeID = GetTypeID();
}
Assert( m_nSOTypeID >= 0 );
#endif // ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
}
#else
virtual ~CSharedObject() {}
#endif
virtual int GetTypeID() const = 0;
virtual bool BParseFromMessage( const CUtlBuffer & buffer ) = 0;
virtual bool BParseFromMessage( const std::string &buffer ) = 0;
virtual bool BUpdateFromNetwork( const CSharedObject & objUpdate ) = 0;
virtual bool BIsKeyLess( const CSharedObject & soRHS ) const = 0;
virtual void Copy( const CSharedObject & soRHS ) = 0;
virtual void Dump() const = 0;
virtual bool BShouldDeleteByCache() const { return true; }
virtual CUtlString GetDebugString() const { return PchClassName( GetTypeID() ); };
bool BIsKeyEqual( const CSharedObject & soRHS ) const;
static void RegisterFactory( int nTypeID, SOCreationFunc_t fnFactory, uint32 unFlags, const char *pchClassName );
static CSharedObject *Create( int nTypeID );
static uint32 GetTypeFlags( int nTypeID );
static const char *PchClassName( int nTypeID );
static const char *PchClassBuildCacheNodeName( int nTypeID );
static const char *PchClassCreateNodeName( int nTypeID );
static const char *PchClassUpdateNodeName( int nTypeID );
#ifdef GC
virtual bool BIsNetworked() const { return true; }
virtual bool BIsDatabaseBacked() const { return true; }
virtual bool BYieldingAddToDatabase();
virtual bool BYieldingWriteToDatabase( const CUtlVector< int > &fields );
virtual bool BYieldingRemoveFromDatabase();
virtual bool BYieldingAddInsertToTransaction( CSQLAccess & sqlAccess ) { return false; }
virtual bool BYieldingAddWriteToTransaction( CSQLAccess & sqlAccess, const CUtlVector< int > &fields ) { return false; }
virtual bool BYieldingAddRemoveToTransaction( CSQLAccess & sqlAccess ) { return false; }
virtual bool BAddToMessage( CUtlBuffer & bufOutput ) const = 0;
virtual bool BAddToMessage( std::string *pBuffer ) const = 0;
virtual bool BAddDestroyToMessage( CUtlBuffer & bufDestroy ) const = 0;
virtual bool BAddDestroyToMessage( std::string *pBuffer ) const = 0;
virtual bool BParseFromMemcached( CUtlBuffer & buffer ) { return false; }
virtual bool BAddToMemcached( CUtlBuffer & bufOutput ) const { return false; }
bool BSendCreateToSteamID( const CSteamID & steamID, const CSteamID & ownerID, uint64 ulVersion ) const;
bool BSendDestroyToSteamID( const CSteamID & steamID, const CSteamID & ownerID, uint64 ulVersion ) const;
#ifdef DBGFLAG_VALIDATE
virtual void Validate( CValidator &validator, const char *pchName );
static void ValidateStatics( CValidator & validator );
#endif
protected:
/*
// Dirty bit modification. Do not call these directly on SharedObjects. Call them
// on the cache that owns the object so they can be added/removed from the right lists.
virtual void DirtyField( int nField ) = 0;
virtual void MakeDatabaseClean() = 0;
virtual void MakeNetworkClean() = 0;
*/
#endif // GC
#ifdef GC
CSharedObject()
{
#if ENABLE_SO_OVERWRITE_PARANOIA
m_pThis = this;
#endif // ENABLE_SO_OVERWRITE_PARANOIA
#if ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
m_nRefCount = 0;
m_nSOTypeID = kSharedObject_UnassignedType;
#endif // ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
}
#endif
private:
#if ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
enum { kSharedObject_UnassignedType = -999 };
#endif // ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
struct SharedObjectInfo_t
{
SOCreationFunc_t m_pFactoryFunction;
uint32 m_unFlags;
const char *m_pchClassName;
CUtlString m_sBuildCacheSubNodeName;
CUtlString m_sUpdateNodeName;
CUtlString m_sCreateNodeName;
};
static CUtlMap<int, SharedObjectInfo_t> sm_mapFactories;
public:
static const CUtlMap<int, SharedObjectInfo_t> & GetFactories() { return sm_mapFactories; }
};
typedef CUtlVectorFixedGrowable<CSharedObject *, 1> CSharedObjectVec;
#ifdef GC
//this class manages the stats for the shared objects, such as how many exist, how many have been sent, how many have been created, how many destroyed
class CSharedObjectStats
{
public:
CSharedObjectStats();
//called to register a shared object class given a type ID and name. This will return false if
//a type of this ID is already registered
void RegisterSharedObjectType( int nTypeID, const char* pszTypeName );
//called to track when a shared object is created/destroyed to update the running total
void TrackSharedObjectLifetime( int nTypeID, int32 nCount );
//called when a shared object is created to track its creation
void TrackSharedObjectSendCreate( int nTypeID, uint32 nCount );
//called when a shared object is destroyed to track counts
void TrackSharedObjectSendDestroy( int nTypeID, uint32 nCount );
//tracks a new subscription
void TrackSubscription( int nTypeID, uint32 nFlags, uint32 nNumSubscriptions );
//called when a shared object is sent
void TrackSharedObjectSend( int nTypeID, uint32 nNumClients, uint32 nMsgSize );
//reset the stats for all the caches
void ResetStats();
//called to start/stop collecting stats
void StartCollectingStats();
void StopCollectingStats();
bool IsCollectingStats() const { return m_bCollectingStats; }
//called to report the currently collected stats
void ReportCollectedStats() const;
private:
struct SOStats_t
{
SOStats_t();
void ResetStats();
//the text name of this cache
CUtlString m_sName;
//the number of outstanding SO cache objects of this type. This is not cleared
uint32 m_nNumActive;
//the type ID of this stat
int m_nTypeID;
//the total number that have been created/destroyed. Active is difference between these
uint32 m_nNumCreated;
uint32 m_nNumDestroyed;
//how many bytes we have sent (raw, and multiplexed to multiple subscribers)
uint64 m_nRawBytesSent;
uint64 m_nMultiplexedBytesSent;
//the number of sends since our last clear
uint32 m_nNumSends;
//how many subscriptions we have gotten by various subscription types
uint32 m_nNumSubOwner;
uint32 m_nNumSubOtherUsers;
uint32 m_nNumSubGameServer;
};
//sort function that controls the order that the SO stats are presented in the report
static bool SortSOStatsSent( const SOStats_t* pLhs, const SOStats_t* pRhs );
static bool SortSOStatsSubscribe( const SOStats_t* pLhs, const SOStats_t* pRhs );
//to compact the memory space for wide ranging type IDs, the list of stats has two components, an array that can be looked up
//via the type ID which maps to a stat index (or invalid index if no mapping is registered)
static const uint16 knInvalidIndex = ( uint16 )-1;
CUtlVector< uint16 > m_vTypeToIndex;
CUtlVector< SOStats_t > m_Stats;
//are we currently collecting stats or not?
bool m_bCollectingStats;
//the time that we've been collecting
CJobTime m_CollectTime;
uint64 m_nMicroSElapsed;
};
//global stat tracker
extern CSharedObjectStats g_SharedObjectStats;
#endif
//----------------------------------------------------------------------------
// Purpose: Templatized function to use as a factory method for
// CSharedObject subclasses
//----------------------------------------------------------------------------
template<typename SharedObjectSubclass_t>
CSharedObject *CreateSharedObjectSubclass()
{
return new SharedObjectSubclass_t();
}
// Version that always asserts and returns NULL, for SOs that should not be auto-created this way
template<int nSharedObjectType>
CSharedObject *CreateSharedObjectSubclassProhibited()
{
AssertMsg( false, "Attempting to auto-create object of type %d which does not allow SO-based creation", nSharedObjectType );
return NULL;
}
#ifdef GC
#define REG_SHARED_OBJECT_SUBCLASS( derivedClass, flags ) GCSDK::CSharedObject::RegisterFactory( derivedClass::k_nTypeID, GCSDK::CreateSharedObjectSubclass<derivedClass>, (flags), #derivedClass )
// GC only -- sharedobjects that cannot be auto-created by the SO code, and might not have a specific derived class that
// publicly implements the interface
#define REG_SHARED_OBJECT_SUBCLASS_BY_ID_NOCREATE( strName, nTypeID, flags ) \
GCSDK::CSharedObject::RegisterFactory( nTypeID, CreateSharedObjectSubclassProhibited<nTypeID>, (flags), strName )
#else
#define REG_SHARED_OBJECT_SUBCLASS( derivedClass ) GCSDK::CSharedObject::RegisterFactory( derivedClass::k_nTypeID, GCSDK::CreateSharedObjectSubclass<derivedClass>, 0, #derivedClass )
#endif
} // namespace GCSDK
#include "tier0/memdbgoff.h"
#endif //SHAREDOBJECT_H
+136
View File
@@ -0,0 +1,136 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Base class for objects that are kept in synch between client and server
//
//=============================================================================
#ifndef SHAREDOBJECTCACHE_H
#define SHAREDOBJECTCACHE_H
#ifdef _WIN32
#pragma once
#endif
#include "sharedobject.h"
namespace GCSDK
{
//----------------------------------------------------------------------------
// Purpose: The part of a shared object cache that handles all objects of a
// single type.
//----------------------------------------------------------------------------
class CSharedObjectTypeCache
{
public:
CSharedObjectTypeCache( int nTypeID );
virtual ~CSharedObjectTypeCache();
int GetTypeID() const { return m_nTypeID; }
uint32 GetCount() const { return m_vecObjects.Count(); }
CSharedObject *GetObject( uint32 nObj ) { return m_vecObjects[nObj]; }
const CSharedObject *GetObject( uint32 nObj ) const { return m_vecObjects[nObj]; }
virtual bool AddObject( CSharedObject *pObject );
virtual bool AddObjectClean( CSharedObject *pObject );
virtual CSharedObject *RemoveObject( const CSharedObject & soIndex );
CSharedObject *RemoveObjectByIndex( uint32 nObj );
void DestroyAllObjects();
void RemoveAllObjectsWithoutDeleting();
virtual void EnsureCapacity( uint32 nItems );
CSharedObject *FindSharedObject( const CSharedObject & soIndex );
virtual void Dump() const;
#ifdef DBGFLAG_VALIDATE
virtual void Validate( CValidator &validator, const char *pchName );
#endif
private:
int FindSharedObjectIndex( const CSharedObject & soIndex ) const;
void AddObjectInternal( CSharedObject *pObject );
CSharedObjectVec m_vecObjects;
int m_nTypeID;
};
//----------------------------------------------------------------------------
// Purpose: A cache of a bunch of shared objects of different types. This class
// is shared between clients, gameservers, and the GC and is
// responsible for sending messages from the GC to cause object
// creation/destruction/updating on the clients/gameservers.
//----------------------------------------------------------------------------
class CSharedObjectCache
{
public:
CSharedObjectCache();
virtual ~CSharedObjectCache();
virtual const CSteamID & GetOwner() const = 0;
bool AddObject( CSharedObject *pSharedObject );
bool BDestroyObject( const CSharedObject & soIndex, bool bRemoveFromDatabase );
CSharedObject *RemoveObject( const CSharedObject & soIndex );
void RemoveAllObjectsWithoutDeleting();
//called to find the type cache for the specified class ID. This will return NULL if one does not exist
CSharedObjectTypeCache *FindBaseTypeCache( int nClassID ) const;
//called to create the specified class ID. If one exists, this is the same as find, otherwise one will be constructed
CSharedObjectTypeCache *CreateBaseTypeCache( int nClassID );
CSharedObject *FindSharedObject( const CSharedObject & soIndex );
template < class T >
T *FindTypedSharedObject( const CSharedObject &soIndex )
{
return assert_cast<T *>( FindSharedObject( soIndex ) );
}
// returns various singleton objects
template< typename SOClass_t >
SOClass_t *GetSingleton() const
{
CSharedObjectTypeCache *pTypeCache = FindBaseTypeCache( SOClass_t::k_nTypeID );
if ( pTypeCache )
{
AssertMsg2( pTypeCache->GetCount() == 0 || pTypeCache->GetCount() == 1, "GetSingleton() called on type %u that has invalid number of items %u.", SOClass_t::k_nTypeID, pTypeCache->GetCount() );
if ( pTypeCache->GetCount() == 1 )
{
return (SOClass_t *)pTypeCache->GetObject( 0 );
}
}
return NULL;
}
void SetVersion( uint64 ulVersion ) { m_ulVersion = ulVersion; }
uint64 GetVersion() const { return m_ulVersion; }
virtual void MarkDirty() {}
virtual void Dump() const;
#ifdef DBGFLAG_VALIDATE
virtual void Validate( CValidator &validator, const char *pchName );
#endif
protected:
virtual CSharedObjectTypeCache *AllocateTypeCache( int nClassID ) const = 0;
CSharedObjectTypeCache *GetTypeCacheByIndex( int nIndex ) { return m_mapObjects.IsValidIndex( nIndex ) ? m_mapObjects.Element( nIndex ) : NULL; }
int GetTypeCacheCount() const { return m_mapObjects.MaxElement(); }
int FirstTypeCacheIndex() { return m_mapObjects.FirstInorder(); }
int NextTypeCacheIndex( int iCurrent ) { return m_mapObjects.NextInorder( iCurrent ); }
int InvalidTypeCacheIndex() { return m_mapObjects.InvalidIndex(); }
uint64 m_ulVersion;
private:
CUtlMap<int, CSharedObjectTypeCache *> m_mapObjects;
};
} // namespace GCSDK
#endif //SHAREDOBJECTCACHE_H
+499
View File
@@ -0,0 +1,499 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Base class for transactions that modify a CGCSharedObjectCache and the database
//
//=============================================================================
#ifndef SHAREDOBJECTTRANSACTION_H
#define SHAREDOBJECTTRANSACTION_H
#ifdef _WIN32
#pragma once
#endif
#include <functional>
namespace GCSDK
{
template < typename TSharedObject >
struct SharedObjectContainsAuditEntryType
{
typedef char t_Yes[1];
typedef char t_No[2];
template < typename T >
static t_Yes& Test( typename T::CAuditEntry * );
template < typename T >
static t_No& Test( ... );
enum { kValue = sizeof( Test<TSharedObject>( NULL ) ) == sizeof( t_Yes ) };
};
//-----------------------------------------------------------------------------
// Purpose: Let's stop writing transactional code by hand everywhere!
//
// Core usage:
//
// - make a new instance, either starting a new SQL transaction or hooking
// into an existing open transaction.
//
// - call some combination of AddNewObject/RemoveObject to add newly-allocated
// objects or remove existing objects. If the types of objects being added/
// removed contain a linked audit data class, you're required to pass in
// a filled-out instance with the add/remove request.
//
// - modify some existing objects. You can either call ModifyObject directly
// or call one of the helper functions/macros (ie., ModifyObjectSch).
// Whatever changes get made here won't be visible anywhere outside this
// transaction object until a commit succeeds.
//
// - try to do a commit. If this succeeds, everything worked and memory has
// been updated to reflect DB changes (if any). If this fails, or if the
// transaction object gets destroyed with an open transaction, all queued
// SQL work will be dropped and no potential memory changes will happen.
//
// That was too long. What's a short version?:
//
// {
// CSharedObjectTransactionEx transaction( pSomeLockedSOCache, "Sample Tool Transaction" );
// transaction.RemoveObject( pToolItem, CEconItem::CAuditEntry( ... ) );
// transaction.RemoveObject( pToolTargetItem, CEconItem::CAuditEntry( ... ) );
// transaction.AddNewObject( pNewResultItem, CEconItem::CAuditEntry( ... ) );
// transaction.ModifyObjectSch( pEconGameAccount, unNumToolsUsed, pEconGameAccount->Obj().m_pEconGameAccount + 1 );
// Verify( transaction.BYieldingCommit() );
// }
//
// Guarantees this class makes:
//
// - if the initial state is correct and client code isn't malicious, a
// lock on the SO cache passed in will be maintained for the lifetime
// of the class.
//
// - externally, no changes will be visible on the GC or on cients until a
// commit attempt takes place. If the commit failures, no changes will
// take place in memory. If the commit succeeds, all memory changes will
// become visible "simultaneously" (no yields, but not atomic from a
// threading perspective.
//
// - this class will do the minimum amount of work possible to guarantee
// correct behavior. If you don't touch any networked objects, no network
// updates will be sent. If you don't touch any DB-backed objects, no DB
// work will be done.
//-----------------------------------------------------------------------------
class CSharedObjectTransactionEx
{
public:
CSharedObjectTransactionEx( CGCSharedObjectCache *pLockedSOCache, const char *pszTransactionName );
~CSharedObjectTransactionEx();
// AddNewObject:
// Add a new object to this user's SO cache. If the type of this object supports audit entries, you're
// required to pass in a CAuditEntry of the appropriate type (ie., CEconItem::CAuditEntry). If your type
// doesn't support audit entries, and you're really sure that not auditing in the correct behavior, you
// call the single-argument version below. In both cases, it's illegal to pass in an object that's
// a raw CSharedObject pointer because then this code doesn't know what type of audit data to look for.
//
// It's possible to set up these functions using SFINAE so that the compiler will only see the appropriate
// version, but this way produces prettier error messages.
// Add an object to this user's cache, conditional on the SQL transaction succeeding, including writing an
// audit entry to SQL explaining where this object came from.
template < typename TSharedObject >
void AddNewObject( TSharedObject *pObject, const typename TSharedObject::CAuditEntry& audit )
{
COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSharedObject, CSharedObject>::kValue) );
COMPILE_TIME_ASSERT( SharedObjectContainsAuditEntryType<TSharedObject>::kValue );
m_bTransactionBuildSuccess &= BAddNewObjectInternal( pObject );
m_bTransactionBuildSuccess &= audit.BAddAuditEntryToTransaction( *m_pSQLAccess, pObject );
}
// Add an object to this user's cache, conditional on the SQL transaction succeeding, but don't write any
// audit data. In general, this is probably the wrong thing to do and you want to be making sure this object
// type supports writing audit data and then writing it.
template < typename TSharedObject >
void AddNewObject( TSharedObject *pObject )
{
COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSharedObject, CSharedObject>::kValue) );
COMPILE_TIME_ASSERT( !SharedObjectContainsAuditEntryType<TSharedObject>::kValue );
m_bTransactionBuildSuccess &= BAddNewObjectInternal( pObject );
}
// This is a helper class purely to help VC will template type deduction. The real signature we want for the
// base ModifyObject function is:
//
// template < typename TSharedObject >
// void ModifyObject( TSharedObject *pObject, const std::function< bool( CSQLAccess&, CSharedObjectDirtyList&, TSharedObject * ) >& funcModifyAndAudit )
//
// ...but if we do do that, VC will complain that it doesn't know how to deduce the type for TSharedObject
// because of the second parameter. Instead, we make it deduce the type based on the first parameter, and then
// feed that type into this helper class to get the type for the second parameter.
template < typename TSharedObject >
struct CSharedObjectModifyAndAuditFunction
{
typedef std::function< bool( CSQLAccess&, CSharedObjectDirtyList&, TSharedObject * ) > ModifyFunctionType;
};
// ModifyObject: takes in
template < typename TSharedObject >
void ModifyObject( TSharedObject *pObject, const typename CSharedObjectModifyAndAuditFunction<TSharedObject>::ModifyFunctionType& funcModifyAndAudit )
{
COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSharedObject, CSharedObject>::kValue) );
CSharedObject *pWritableObject = NULL;
m_bTransactionBuildSuccess &= BTrackModifiedObjectInternal( pObject, &pWritableObject );
AssertMsg( !m_bTransactionBuildSuccess || pWritableObject != NULL, "Cannot be tracking state for an object but not having a writable version!" );
m_bTransactionBuildSuccess &= funcModifyAndAudit( *m_pSQLAccess, m_SODirtyList, assert_cast<TSharedObject *>( pWritableObject ) );
}
// ModifyObjectL() is a helper macro designed to behave like a function. It takes as parameters the original
// object you want to modify and the code you want to run on it, expressed as a lambda.
#define ModifyObjectL( obj_, modifyfunc_ ) \
ModifyObject( obj_, [=] ( CSQLAccess& sqlAccess, CSharedObjectDirtyList& SODirtyList, decltype( obj_ ) pWritableObject ) -> bool { modifyfunc_ } )
// ModifyObjectSch() is a helper macro designed to behave like a function. It takes as parameters the original
// object you want to modify, an identifier for the field you want to change, and the new value you want to
// change it to. Ex.:
//
// transaction.ModifyObjectSch( pLockedSOCache->GetGameAccount(), // type of internal object is used to look up field IDs
// unNextHalloweenGiftTime, // turns into "(obj).m_unNextHalloweenGiftTime" and "(type)::k_iField_unNextHalloweenGiftTime"
// CRTime::RTime32DateAdd( CRTime::RTime32TimeCur(), tf_halloween_min_minutes_between_drops_per_player.GetInt(), k_ETimeUnitMinute ) );
#define ModifyObjectSch( obj_, field_, newvalue_ ) \
ModifyObjectL( obj_, \
{ \
pWritableObject->Obj().m_ ## field_ = (newvalue_); \
SODirtyList.DirtyObjectField( pWritableObject, std::remove_reference< decltype( pWritableObject->Obj() ) >::type::k_iField_ ## field_ ); \
return true; \
} )
// ModifyObjectProto() is a helper macro designed to behave like a function. It takes as parameters the original
// object you want to modify, the field name in the message or the field you want to change, the identifier field
// ID (will be identical except for case/underscores), and the new value. Ex.:
//
// soTrans.ModifyObjectProto( pGameAccountClient, // type of internal object is used to look up field IDs
// preview_item_def, // turns into "(obj)->set_preview_item_def"
// PreviewItemDef, // turns into "(type)::kPreviewItemDefFieldNumber"
// 0 );
#define ModifyObjectProto( obj_, fieldfunc_, fieldname_, newvalue_ ) \
ModifyObjectL( obj_, \
{ \
pWritableObject->Obj().set_ ## fieldfunc_( newvalue_ ); \
SODirtyList.DirtyObjectField( pWritableObject, std::remove_reference< decltype( pWritableObject->Obj() ) >::type::k ## fieldname_ ## FieldNumber ); \
return true; \
} )
// RemoveObject
template < typename TSharedObject >
void RemoveObject( TSharedObject *pObject, const typename TSharedObject::CAuditEntry& audit )
{
COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSharedObject, CSharedObject>::kValue) );
COMPILE_TIME_ASSERT( SharedObjectContainsAuditEntryType<TSharedObject>::kValue );
m_bTransactionBuildSuccess &= BRemoveObjectInternal( pObject );
m_bTransactionBuildSuccess &= audit.BAddAuditEntryToTransaction( *m_pSQLAccess, pObject );
}
template < typename TSharedObject >
void RemoveObject( CSharedObject *pObject )
{
COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSharedObject, CSharedObject>::kValue) );
COMPILE_TIME_ASSERT( !SharedObjectContainsAuditEntryType<TSharedObject>::kValue );
m_bTransactionBuildSuccess &= BRemoveObjectInternal( pObject );
}
template < class TSchType >
void AddSQLRecord( const TSchType& sch )
{
// We can't test for all types here because there's no compile-time list. This is
// mostly to demonstrate "seriously please don't call this with types that have
// CSharedObject wrappers".
COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSchType, CSchItem>::kValue) );
COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSchType, CSchItemAudit>::kValue) );
COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSchType, CSchGameAccount>::kValue) );
// We're about to call a function with "Yielding" in the name and we aren't in a
// function with "Yielding" in the name, but we *are* in a transaction so we know
// we don't yield. We verify that assumption here.
DO_NOT_YIELD_THIS_SCOPE();
// Queue up the work for SQL as long as we're in a good state to do so.
m_bTransactionBuildSuccess &= BIsValidInternalState()
? m_pSQLAccess->BYieldingInsertRecord( &sch )
: false;
}
template < class TSchType >
void AddOrUpdateSQLRecord( TSchType& sch )
{
// We can't test for all types here because there's no compile-time list. This is
// mostly to demonstrate "seriously please don't call this with types that have
// CSharedObject wrappers".
COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSchType, CSchItem>::kValue) );
COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSchType, CSchItemAudit>::kValue) );
COMPILE_TIME_ASSERT( (!AreTypesIdentical<TSchType, CSchGameAccount>::kValue) );
// We're about to call a function with "Yielding" in the name and we aren't in a
// function with "Yielding" in the name, but we *are* in a transaction so we know
// we don't yield. We verify that assumption here.
DO_NOT_YIELD_THIS_SCOPE();
// Queue up the work for SQL as long as we're in a good state to do so.
m_bTransactionBuildSuccess &= BIsValidInternalState()
? m_pSQLAccess->BYieldingInsertOrUpdateOnPK( &sch )
: false;
}
// This is meant purely for interop with the SQL message queue and even then only as a temporary
// measure until we have a CSQLTransaction object we can pass around instead.
CSQLAccess& GetSQLTransactionForSQLMsgQueue() { return *m_pSQLAccess; }
// slow! but fine for current uses
template < class TSharedObject >
const TSharedObject *FindTypedSharedObject( const CSharedObject &soIndex ) const
{
return assert_cast<TSharedObject *>( InternalFindSharedObject( pSOCache, soIndex ) );
}
// Take all the work we queued up for SQL and try to commit it to the DB. If that works, take all
// of our memory changes and copy them over. From the outside, this will either move *all* memory
// changes to our cache over at once, or not touch any in-memory structures at all.
MUST_CHECK_RETURN bool BYieldingCommit();
// Cancel this transaction completely -- this will empty the queue of whatever SQL work we may have
// done and also free up the memory we used to track modified object state, etc. Once this function
// gets called, it's illegal to call any other functions on this transaction object except the
// destructor.
void Rollback();
private:
// State validation. Non-const because may set internal error state.
bool BIsValidInternalState();
bool BIsValidInput( const CSharedObject *pObject );
const char *GetInternalTransactionDesc() const { return m_pSQLAccess->PchTransactionName(); }
template < typename tCommitInfo >
const tCommitInfo *InternalFindCommitInfo( const CSharedObject *pObject, const CUtlVector<tCommitInfo>& vec ) const
{
FOR_EACH_VEC( vec, i )
{
if ( vec[i].m_pObject == pObject )
return &vec[i];
}
return NULL;
}
const CSharedObject *InternalFindSharedObject( CGCSharedObjectCache *pSOCache, const CSharedObject& soIndex ) const;
// Will return NULL if pre-commit operations/checks were successful, or a pointer to a descriptive error string if
// pre-commit failed.
const char *InternalPreCommit();
bool BAddNewObjectInternal( CSharedObject *pObject );
bool BTrackModifiedObjectInternal( CSharedObject *pObject, CSharedObject **out_ppWritableObject );
bool BRemoveObjectInternal( CSharedObject *pObject );
friend class CTrustedHelper_OutputAndSetErrorState;
void SetErrorState() { m_bTransactionBuildSuccess = false; }
private:
CGCSharedObjectCache *m_pLockedSOCache; // we don't do any locking ourself, but verify that the lock is held during construction/modification
CSharedObjectDirtyList m_SODirtyList;
CSQLAccess *m_pSQLAccess; // our access to SQL -- may point to our inline instance or may point to an external object if we're hitching on an already-existing transaction
bool m_bTransactionBuildSuccess;
CSQLAccess m_sqlAccessInternal;
struct CreateOrDestroyCommitInfo_t
{
CreateOrDestroyCommitInfo_t( CSharedObject *pObject ) : m_pObject( pObject ) { Assert( m_pObject ); }
CSharedObject *m_pObject;
};
struct ModifyCommitInfo_t
{
ModifyCommitInfo_t( CSharedObject *pWriteableObject, CSharedObject *pOriginalCopy )
: m_pWriteableObject( pWriteableObject )
, m_pObject( pOriginalCopy )
{
}
CSharedObject *m_pWriteableObject; // scratch/memory-writable copy while transaction is open
CSharedObject *m_pObject; // original copy
};
CUtlVector<CreateOrDestroyCommitInfo_t> m_vecObjects_Added;
CUtlVector<CreateOrDestroyCommitInfo_t> m_vecObjects_Removed;
CUtlVector<ModifyCommitInfo_t> m_vecObjects_Modified;
};
class CSharedObjectTransaction
{
public:
/**
* Constructor that will begin a transaction
* @param sqlAccess
* @param pName
*/
CSharedObjectTransaction( CSQLAccess &sqlAccess, const char *pName );
/**
* Destructor
*/
~CSharedObjectTransaction();
/**
* Adds an object that exists in the given CGCSharedObjectCache to be managed in this transaction.
* Call this before making any modifications to the object
* @param pSOCache the owner CGCSharedObjectCache
* @param pObject the object that will be modified
*/
void AddManagedObject( CGCSharedObjectCache *pSOCache, CSharedObject *pObject );
/**
* Adds a brand new object to the given CGCSharedObjectCache
* @param pSOCache the owner CGCSharedObjectCache
* @param pObject the newly created object
*/
void AddNewObject( CGCSharedObjectCache *pSOCache, CSharedObject *pObject );
/**
* Removes an existing object from the CGCSharedObjectCache
* @param pSOCache the owner CGCSharedObjectCache
* @param pObject the object to be removed from the CGCSharedObjectCache
*/
void RemoveObject( CGCSharedObjectCache *pSOCache, CSharedObject *pObject );
/**
* Marks in the transaction that the object was modified. The object must have been previously added via
* the AddManagedObject() call in order for the object to be marked dirty. If the object is new to the
* CGCSharedObjectCache, then calling this will return false (which is not necessarily an error)
*
* @param pObject the object that will be modified
* @param unFieldIdx the field that was changed
*/
void ModifiedObject( CGCSharedObjectCache *pSOCache, CSharedObject *pObject, uint32 unFieldIdx );
/**
* @param pSOCache
* @param soIndex
* @return the CSharedObject that matches either in the CGCSharedObjectCache or to be added
*/
template < class T >
T *FindTypedSharedObject( CGCSharedObjectCache *pSOCache, const CSharedObject &soIndex )
{
return assert_cast<T *>( FindSharedObject( pSOCache, soIndex ) );
}
/**
* Rolls back any changes made to the objects in-memory and in the database
*
* This function should not be made virtual -- it's called from within the destructor.
*/
void Rollback();
/**
* Commits any changes to the database and also to memory
* @return true if successful, false otherwise
*/
bool BYieldingCommit( bool bAllowEmpty = false );
/**
* @return GCSDK::CSQLAccess associated with this transaction
*/
CSQLAccess &GetSQLAccess() { return m_sqlAccess; }
/**
* Fetch name of transaction for debugging purposes
* @return the string passed to the constructor
*/
const char *PchName() const;
private:
/**
* @param pSOCache
* @param soIndex
* @return the CSharedObject that matches either in the list of currently-modified objects or the list
* or of new objects we added; this will not search in the base SO cache
*/
CSharedObject *FindSharedObject( CGCSharedObjectCache *pSOCache, const CSharedObject &soIndex );
/**
* Reverts all in-memory modifications and deletes all newly created objects.
*
* This function should not be made virtual -- it's called from within the destructor.
*/
void Undo();
/**
* Set an error string to describe an error we encountered building this transaction. Setting this
* will cause the transaction to fail.
*/
void SetError( const char *pszNewError )
{
AssertMsg( pszNewError && ( pszNewError[0] != '\0' ), "Invalid NULL/empty error set in CSharedObjectTransaction::SetError()! This will have the effect of clearing the error state, which is unsupported." );
m_sErrorDesc = pszNewError;
}
/**
* Clear our error string if we have one. This will allow transactions to succeed and so is only intended
* to be done when the transaction itself has either completed (no either to begin with) or emptied (clean
* slate).
*/
void ClearError()
{
Assert( m_vecObjects_Added.Count() == 0 );
Assert( m_vecObjects_Removed.Count() == 0 );
Assert( m_vecObjects_Modified.Count() == 0 );
m_sErrorDesc.Clear();
}
/**
* Get access to the string describing what, if any, the last error we encountered was. Will return
* NULL if no errors have been encountered.
*/
const char *GetError() const
{
return m_sErrorDesc.IsEmpty() ? NULL : m_sErrorDesc.String();
}
struct undoinfo_t
{
CSharedObject *pObject;
CGCSharedObjectCache *pSOCache;
CSharedObject *pOriginalCopy;
bool operator==( const undoinfo_t& other ) const { return other.pObject == pObject && other.pSOCache == pSOCache && other.pOriginalCopy == pOriginalCopy; }
};
// Wraps the common check to make sure these pointers are valid and that the cache is locked. This is non-const
// because it can call SetError().
bool AssertValidInput( const CGCSharedObjectCache *pSOCache, const CSharedObject *pObject, const char *pszContext );
// Finds the object in the given vector (using simple pointer compare)
undoinfo_t *FindObjectInVector( const CSharedObject *pObject, CUtlVector<undoinfo_t> &vec ) const;
// variables
CUtlVector< undoinfo_t > m_vecObjects_Added;
CUtlVector< undoinfo_t > m_vecObjects_Removed;
CUtlVector< undoinfo_t > m_vecObjects_Modified;
CSQLAccess &m_sqlAccess;
// internal error state
CUtlString m_sErrorDesc; // will be non-empty if we've encountered an error at some point building this transaction
}; // class CSharedObjectTransaction
}; // namespace GCSDK
#endif // SHAREDOBJECTTRANSACTION_H
+219
View File
@@ -0,0 +1,219 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Sets of columns in queries
//
// $NoKeywords: $
//=============================================================================
#ifndef COLUMNSET_H
#define COLUMNSET_H
#ifdef _WIN32
#pragma once
#endif
namespace GCSDK
{
//-----------------------------------------------------------------------------
// Purpose: Sets of columns in queries
//-----------------------------------------------------------------------------
class CColumnSet
{
public:
CColumnSet( const CRecordInfo *pRecordInfo );
CColumnSet( const CColumnSet & rhs );
CColumnSet & operator=( const CColumnSet & rhs );
const CColumnSet operator+( const CColumnSet & rhs ) const;
CColumnSet & operator+=( const CColumnSet & rhs );
//NOTE: These do not ensure uniqueness, the CSET_ macros below should be used instead as they will compile time enforce uniqueness
CColumnSet( const CRecordInfo *pRecordInfo, int col1 );
CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2 );
CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3 );
CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3, int col4 );
CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3, int col4, int col5 );
CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3, int col4, int col5, int col6 );
CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3, int col4, int col5, int col6, int col7 );
CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3, int col4, int col5, int col6, int col7, int col8 );
void BAddColumn( int nColumn );
void BRemoveColumn( int nColumn );
bool IsSet( int nColumn ) const;
bool IsEmpty() const { return m_vecColumns.Count() == 0;}
uint32 GetColumnCount() const;
int GetColumn( int nIndex ) const;
const CColumnInfo & GetColumnInfo( int nIndex ) const;
const CRecordInfo *GetRecordInfo() const { return m_pRecordInfo; }
void MakeEmpty();
void MakeFull();
void MakeInsertable();
void MakeNoninsertable();
void MakePrimaryKey();
void MakeInverse( const CColumnSet & columnSet );
//determines if the current column set has all fields set. Useful for detection of new columns being added to the schema
bool BAreAllFieldsSet() const;
template< typename TSchClass >
static CColumnSet Empty();
template< typename TSchClass >
static CColumnSet Full();
template< typename TSchClass >
static CColumnSet Insertable();
template< typename TSchClass >
static CColumnSet Noninsertable();
template< typename TSchClass >
static CColumnSet PrimaryKey();
static CColumnSet Inverse( const CColumnSet & columnSet );
#ifdef DBGFLAG_VALIDATE
void Validate( CValidator &validator, const char *pchName );
#endif
private:
CUtlVector<int> m_vecColumns;
const CRecordInfo *m_pRecordInfo;
};
//this is a utility class which can at compile time ensure that all of the provided column values are unique and will generate an error if that doesn't
//hold true. The default values just need to be unique and greater than what is expected for real columns
template < int n1 = 10001, int n2 = 10002, int n3 = 10003, int n4 = 10004, int n5 = 10005, int n6 = 10006, int n7 = 10007, int n8 = 10008 >
class CUniqueColChecker
{
public:
//this is a simple pass through so that it can wrap the declaration of a column set
static const CColumnSet& VerifyUnique( const CColumnSet& cs )
{
COMPILE_TIME_ASSERT( n1 != n2 && n1 != n3 && n1 != n4 && n1 != n5 && n1 != n6 && n1 != n7 && n1 != n8 );
COMPILE_TIME_ASSERT( n2 != n3 && n2 != n4 && n2 != n5 && n2 != n6 && n2 != n7 && n2 != n8 );
COMPILE_TIME_ASSERT( n3 != n4 && n3 != n5 && n3 != n6 && n3 != n7 && n3 != n8 );
COMPILE_TIME_ASSERT( n4 != n5 && n4 != n6 && n4 != n7 && n4 != n8 );
COMPILE_TIME_ASSERT( n5 != n6 && n5 != n7 && n5 != n8 );
COMPILE_TIME_ASSERT( n6 != n7 && n6 != n8 );
COMPILE_TIME_ASSERT( n7 != n8 );
return cs;
}
};
// Usage notes:
// The fields in a column set are order-dependent, and must match the order of the fields in
// the query used to generate the data. The code that reads values doesn't do any fancy
// name-matching and will copy values to incorrect locations silently if there is a
// disagreement between the fields in the query and the fields in the column set.
//
// Examples:
// // This is broken.
// query = "SELECT * FROM Items";
// columnSet = CSET_12_COL( CSchItem, individual_field_names );
//
// // This is fixed.
// query = "SELECT * FROM Items";
// columnSet = CSET_FULL( ... );
#define FOR_EACH_COLUMN_IN_SET( columnSet, iterName ) for( uint32 iterName = 0; iterName < (columnSet).GetColumnCount(); iterName++ )
#define CSET_EMPTY( schClass ) CColumnSet::Empty<schClass>()
#define CSET_FULL( schClass ) CColumnSet::Full<schClass>()
#define CSET_INSERTABLE( schClass ) CColumnSet::Insertable<schClass>()
#define CSET_NONINSERTABLE( schClass ) CColumnSet::Noninsertable<schClass>()
#define CSET_PK( schClass ) CColumnSet::PrimaryKey<schClass>()
#define CSET_1_COL( schClass, col1 ) \
CColumnSet( GSchemaFull().GetSchema( schClass::k_iTable ).GetRecordInfo(), schClass::col1 )
#define CSET_2_COL( schClass, col1, col2 ) \
CUniqueColChecker< schClass::col1, schClass::col2 >::VerifyUnique( \
CColumnSet( GSchemaFull().GetSchema( schClass::k_iTable ).GetRecordInfo(), schClass::col1, schClass::col2 ) )
#define CSET_3_COL( schClass, col1, col2, col3 ) \
CUniqueColChecker< schClass::col1, schClass::col2, schClass::col3 >::VerifyUnique( \
CColumnSet( GSchemaFull().GetSchema( schClass::k_iTable ).GetRecordInfo(), schClass::col1, schClass::col2, schClass::col3 ) )
#define CSET_4_COL( schClass, col1, col2, col3, col4 ) \
CUniqueColChecker< schClass::col1, schClass::col2, schClass::col3, schClass::col4 >::VerifyUnique( \
CColumnSet( GSchemaFull().GetSchema( schClass::k_iTable ).GetRecordInfo(), schClass::col1, schClass::col2, schClass::col3, schClass::col4 ) )
#define CSET_5_COL( schClass, col1, col2, col3, col4, col5 ) \
CUniqueColChecker< schClass::col1, schClass::col2, schClass::col3, schClass::col4, schClass::col5 >::VerifyUnique( \
CColumnSet( GSchemaFull().GetSchema( schClass::k_iTable ).GetRecordInfo(), schClass::col1, schClass::col2, schClass::col3, schClass::col4, schClass::col5 ) )
#define CSET_6_COL( schClass, col1, col2, col3, col4, col5, col6 ) \
CUniqueColChecker< schClass::col1, schClass::col2, schClass::col3, schClass::col4, schClass::col5, schClass::col6 >::VerifyUnique( \
CColumnSet( GSchemaFull().GetSchema( schClass::k_iTable ).GetRecordInfo(), schClass::col1, schClass::col2, schClass::col3, schClass::col4, schClass::col5, schClass::col6 ) )
#define CSET_7_COL( schClass, col1, col2, col3, col4, col5, col6, col7 ) \
CUniqueColChecker< schClass::col1, schClass::col2, schClass::col3, schClass::col4, schClass::col5, schClass::col6, schClass::col7 >::VerifyUnique( \
CColumnSet( GSchemaFull().GetSchema( schClass::k_iTable ).GetRecordInfo(), schClass::col1, schClass::col2, schClass::col3, schClass::col4, schClass::col5, schClass::col6, schClass::col7 ) )
#define CSET_8_COL( schClass, col1, col2, col3, col4, col5, col6, col7, col8 ) \
CUniqueColChecker< schClass::col1, schClass::col2, schClass::col3, schClass::col4, schClass::col5, schClass::col6, schClass::col7, schClass::col8 >::VerifyUnique( \
CColumnSet( GSchemaFull().GetSchema( schClass::k_iTable ).GetRecordInfo(), schClass::col1, schClass::col2, schClass::col3, schClass::col4, schClass::col5, schClass::col6, schClass::col7, schClass::col8 ) )
//-----------------------------------------------------------------------------
// Purpose: Returns an empty Column Set for a schema class
//-----------------------------------------------------------------------------
template< typename TSchClass >
CColumnSet CColumnSet::Empty()
{
CColumnSet set( GSchemaFull().GetSchema( TSchClass::k_iTable ).GetRecordInfo() );
return set;
}
//-----------------------------------------------------------------------------
// Purpose: Returns a Column Set for a schema class which contains every field
//-----------------------------------------------------------------------------
template< typename TSchClass >
CColumnSet CColumnSet::Full()
{
CColumnSet set( GSchemaFull().GetSchema( TSchClass::k_iTable ).GetRecordInfo() );
set.MakeFull();
return set;
}
//-----------------------------------------------------------------------------
// Purpose: Returns a Column Set for a schema class which contains every
// insertable field
//-----------------------------------------------------------------------------
template< typename TSchClass >
CColumnSet CColumnSet::Insertable()
{
CColumnSet set( GSchemaFull().GetSchema( TSchClass::k_iTable ).GetRecordInfo() );
set.MakeInsertable();
return set;
}
//-----------------------------------------------------------------------------
// Purpose: Returns a Column Set for a schema class which contains every
// noninsertable field
//-----------------------------------------------------------------------------
template< typename TSchClass >
CColumnSet CColumnSet::Noninsertable()
{
CColumnSet set( GSchemaFull().GetSchema( TSchClass::k_iTable ).GetRecordInfo() );
set.MakeNoninsertable();
return set;
}
//-----------------------------------------------------------------------------
// Purpose: Returns a Column Set for a schema class which contains every
// primary key field
//-----------------------------------------------------------------------------
template< typename TSchClass >
CColumnSet CColumnSet::PrimaryKey()
{
CColumnSet set( GSchemaFull().GetSchema( TSchClass::k_iTable ).GetRecordInfo() );
set.MakePrimaryKey();
return set;
}
} // namespace GCSDK
#endif // COLUMNSET_H
+467
View File
@@ -0,0 +1,467 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================
#ifndef GCRECORD_H
#define GCRECORD_H
#ifdef _WIN32
#pragma once
#endif
#include "schema.h"
#include "tier0/memdbgon.h"
namespace GCSDK
{
#pragma pack( push, 1 )
class CRecordInfo;
//-----------------------------------------------------------------------------
// VarFieldBlockInfo_t
// Tracks a block of memory used to hold all the variable-length
// fields for a record.
//-----------------------------------------------------------------------------
struct VarFieldBlockInfo_t
{
union
{
// Take up 64-bits of space now even though
// pointers are still 32 bits
uint8* m_pubBlock;
uint64 _unused;
};
uint32 m_cubBlock; // how much is in this block?
uint32 m_cubBlockFree; // how much in this block is free?
};
//-----------------------------------------------------------------------------
// VarField_t
// Data format for a variable field entry in a DS record
// For leaf code, is hidden inside a CVarField or CVarCharField
//-----------------------------------------------------------------------------
struct VarField_t
{
uint32 m_cubField; // Size of the field
uint32 m_dubOffset; // Offset of the field within the block
};
//-----------------------------------------------------------------------------
// CVarField
// Defines a class to encompass a variable-length field - opaque
//-----------------------------------------------------------------------------
class CVarField : private VarField_t
{
public:
friend class CRecordVar;
private:
};
//-----------------------------------------------------------------------------
// CVarCharField
// Defines a class to encompass a variable-length string field - opaque
//-----------------------------------------------------------------------------
class CVarCharField : public CVarField
{
public:
friend class CRecordVar;
};
//-----------------------------------------------------------------------------
// CVarCharField
// Defines a class to encompass a variable-length string field - opaque
//-----------------------------------------------------------------------------
class CVarBinaryField : public CVarField
{
public:
friend class CRecordVar;
};
//-----------------------------------------------------------------------------
// CImageField
// Defines a class to encompass a long variable-length binary field - opaque
//-----------------------------------------------------------------------------
class CImageField : public CVarField
{
public:
friend class CRecordVar;
};
#pragma pack( pop )
// fix the size of this just to be safe
#pragma pack( push, 4 )
class CSchema;
//-----------------------------------------------------------------------------
// CRecordBase
// Defines a class which arbitrates access to a fixed-length record
//
// This is used as a base class for the CSchTable wrapper classes emitted
// by the schema compiler when the involved table has no variable length data.
//-----------------------------------------------------------------------------
class CRecordBase
{
public:
// These both allocate new space and COPY the record data into it
CRecordBase( const CRecordBase &that );
CRecordBase &operator=(const CRecordBase &that);
// Init from general memory
int InitFromBytes( uint8 *pubRecord );
virtual ~CRecordBase();
// Use these when sending records over the wire
uint32 CubSerialized();
virtual uint8 *PubRecordFixed();
const uint8 *PubRecordFixed() const;
uint32 CubRecordFixed() const;
virtual uint8 *PubRecordVarBlock();
virtual const uint8 *PubRecordVarBlock() const;
uint32 CubRecordVarBlock() const;
bool BAssureRecordVarStorage( uint32 cVariableBytes );
// generic data accessors
bool BGetField( int iField, uint8 **ppubData, uint32 *pcubField ) const;
virtual bool BSetField( int iField, void * pvData, uint32 cubData );
virtual void WipeField( int iField );
// data accessors
const char * GetStringField( int iField, uint32 *pcubField );
int GetInt( int iField );
uint16 GetUint16( int iField );
uint32 GetUint32( int iField );
uint64 GetUint64( int iField );
// variable length data accessors
// (not implemented by this class)
virtual const char *ReadVarCharField( const CVarCharField &field ) const;
virtual const uint8 *ReadVarDataField( const CVarField &field, uint32 *pcubField ) const;
virtual bool SetVarCharField( CVarCharField &field, const char *pchString, bool bTruncate, int32 iField );
virtual void SetVarDataField( CVarField &field, const void *pvData, uint32 cubData );
virtual const CSchema *GetPSchema() const
{
return const_cast<CRecordBase*>(this)->GetPSchema();
}
virtual CSchema *GetPSchema();
const CRecordInfo *GetPRecordInfo() const;
// implemented by CSch-something derivatives
virtual int GetITable() const = 0;
void RenderField( uint32 unColumn, int cchBuffer, char *pchBuffer ) const;
#ifdef DBGFLAG_VALIDATE
void Validate( CValidator &validator, const char *pchName ); // Validate our internal structures
static void ValidateStatics( CValidator &validator, const char *pchName );
#endif // DBGFLAG_VALIDATE
protected:
// copies the contents of the record. The assignement operator uses this internally
virtual void Copy( const CRecordBase & that );
CSchema *GetPSchemaImpl();
void Cleanup();
bool BSetField( int iField, void *pvData, uint32 cubData, bool *pbRealloced );
// ctor for derived classes, CSch*
CRecordBase( ) { }
};
//-----------------------------------------------------------------------------
// CRecordVar
// Defines a class which arbitrates access to a variable-length record
//
// This is used as a base class for the CSchTable wrapper classes emitted
// by the schema compiler when the involved table *does* have variable-length data
//-----------------------------------------------------------------------------
class CRecordVar : public CRecordBase
{
public:
CRecordVar( )
{
m_pSchema = NULL;
m_nFlags = 0;
}
virtual ~CRecordVar()
{
Cleanup();
}
virtual uint8* PubRecordFixed();
const uint8 *PubRecordFixed() const;
virtual CSchema *GetPSchema()
{
return m_pSchema;
}
virtual const CSchema *GetPSchema() const
{
return m_pSchema;
}
// Init from general memory
int InitFromBytes( uint8 *pubRecord );
// generic data accessors
virtual bool BSetField( int iField, void * pvData, uint32 cubData );
virtual void WipeField( int iField );
// variable-length data accessors
virtual const char *ReadVarCharField( const CVarCharField &field ) const;
virtual const uint8 *ReadVarDataField( const CVarField &field, uint32 *pcubField ) const;
virtual bool SetVarCharField( CVarCharField &field, const char *pchString, bool bTruncate, int32 iField );
virtual void SetVarDataField( CVarField &field, const void *pvData, uint32 cubData );
// allocated fixed means we've got our own memory for the fixed record
// allocated var block means we've allocated a block for the variable part of this record
enum EFlags { k_EAllocatedFixed = 0x1, k_EAllocatedVarBlock = 0x2 };
bool BFlagSet( int eFlag ) const;
#ifdef DBGFLAG_VALIDATE
void Validate( CValidator &validator, const char *pchName ); // Validate our internal structures
#endif // DBGFLAG_VALIDATE
protected:
// copies the contents of the record. The assignement operator uses this internally
virtual void Copy( const CRecordBase & that );
// initialization helper
void DoInit()
{
m_pSchema = CRecordBase::GetPSchema();
}
void SetFlag( int eFlag, bool bSet );
void Cleanup();
CSchema *m_pSchema; // Corresponding Schema
int m_nFlags; // Flags about the record memory allocations / location
};
//-----------------------------------------------------------------------------
// CRecordExternal
// Defines a class which arbitrates access to a variable-length record
//
// This is used as an accessor for a polymorphic record. It can be used to
// read CSchTable records when the type is unknown, manipulate stats records,
// or touch data that isn't preallocated. Its use is relatively rare.
//-----------------------------------------------------------------------------
class CRecordExternal : public CRecordVar
{
public:
CRecordExternal()
{
m_pubRecordFixedExternal = NULL;
m_nFlags = 0;
}
virtual ~CRecordExternal()
{
Cleanup();
}
virtual uint8* PubRecordFixed();
const uint8 *PubRecordFixed() const;
void DeSerialize( uint8 *pubData );
int GetITable() const
{
return -1;
}
void Init( CSchema *pSchema );
int Init( CSchema *pSchema, uint8 *pubRecord, bool bTakeOwnership );
// test helpers
void InitRecordRandom( uint32 unPrimaryIndex );
void SetFieldRandom( int iField );
#ifdef DBGFLAG_VALIDATE
void Validate( CValidator &validator, const char *pchName ); // Validate our internal structures
#endif // DBGFLAG_VALIDATE
protected:
// copies the contents of the record. The assignement operator uses this internally
virtual void Copy( const CRecordBase & that );
void Cleanup();
void DoInit()
{
m_pSchema = CRecordBase::GetPSchema();
}
uint8 *m_pubRecordFixedExternal; // If the fixed record is not a part of this object,
// this points to where it is
CSchema *GetPSchemaImpl();
};
//-----------------------------------------------------------------------------
// Accessors for variable length character data.
// These goofy little macros implement some syntax sugar
//-----------------------------------------------------------------------------
#define READ_VAR_CHAR_FIELD( record, field )\
(record).ReadVarCharField( (record).field )
#define WRITE_VAR_CHAR_FIELD( record, field, text )\
(record).SetVarCharField( (record).m_##field, text, false, (record).k_iField_##field )
#define WRITE_VAR_CHAR_FIELD_TRUNC( record, field, text )\
(record).SetVarCharField( (record).m_##field, text, true, (record).k_iField_##field )
#pragma pack( pop )
//-----------------------------------------------------------------------------
// Template classes that get a LessFunc that sorts CRecordBases by a field
// within them
//-----------------------------------------------------------------------------
template <class T, int I, typename F>
class CDefSchOps
{
public:
static bool LessFunc( const T &lhs, const T &rhs )
{
// Check that the field number is valid
COMPILE_TIME_ASSERT( I >= 0 && I < T::k_iFieldMax );
// Check to make sure this is a fixed field
const Field_t &fieldInfo = lhs.GetPSchema()->GetField( I );
Assert( !fieldInfo.BIsStringType() && !fieldInfo.BIsVariableLength() );
if ( fieldInfo.BIsStringType() || fieldInfo.BIsVariableLength() )
return false;
// Read the data and make sure the sizes are correct for the field type we expect
uint8 *pubLhs;
uint8 *pubRhs;
bool bRet;
uint32 cubRead;
bRet = lhs.BGetField( I, &pubLhs, &cubRead );
Assert( bRet && cubRead == sizeof( F ) );
if ( !bRet || cubRead != sizeof( F ) )
return false;
bRet = rhs.BGetField( I, &pubRhs, &cubRead );
Assert( bRet && cubRead == sizeof( F ) );
if ( !bRet || cubRead != sizeof( F ) )
return false;
// Finally do the comparison
return ( *( (F *)pubLhs ) ) < ( *( (F *)pubRhs ) );
}
static bool LessFuncCtx( const T &lhs, const T &rhs, void *pCtx )
{
return LessFunc( lhs, rhs );
}
};
#define DefSchLessFunc( RecordType, FieldIndex, FieldType ) CDefSchOps<RecordType, FieldIndex, FieldType>::LessFunc
#define DefSchLessFuncCtx( RecordType, FieldIndex, FieldType ) CDefSchOps<RecordType, FieldIndex, FieldType>::LessFuncCtx
//-----------------------------------------------------------------------------
// Specializations for string fields
//-----------------------------------------------------------------------------
template <class T, int I>
class CDefSchOps<T, I, char *>
{
public:
static bool LessFunc( const T &lhs, const T &rhs )
{
// Check that the field number is valid
COMPILE_TIME_ASSERT( I >= 0 && I < T::k_iFieldMax );
// Check to make sure this is indeed a string field
Field_t &fieldInfo = lhs.GetPSchema()->GetField( I );
Assert( fieldInfo.BIsStringType() );
if ( !fieldInfo.BIsStringType() )
return false;
// Read the data
uint32 cubRead;
const char *pchLhs = lhs.GetStringField( I, &cubRead );
const char *pchRhs = rhs.GetStringField( I, &cubRead );
// Finally do the comparison
return CDefOps<const char *>::LessFunc( lhs, rhs );
}
static bool LessFuncCtx( const T &lhs, const T &rhs, void *pCtx )
{
return LessFunc( lhs, rhs );
}
};
template <class T, int I>
class CDefSchOps<T, I, const char *>
{
public:
static bool LessFunc( const T &lhs, const T &rhs )
{
return CDefSchOps<T, I, char *>::LessFunc( lhs, rhs );
}
static bool LessFuncCtx( const T &lhs, const T &rhs, void *pCtx )
{
return LessFunc( lhs, rhs );
}
};
//-----------------------------------------------------------------------------
// Purpose: Provide a convenient object to pass around to represent a type
// of record
//-----------------------------------------------------------------------------
class CRecordType
{
public:
virtual int GetITable() const = 0;
virtual CRecordBase *Create() const = 0;
CSchema *GetSchema() const;
CRecordInfo *GetRecordInfo() const;
protected:
private:
};
template <typename TRecord>
class CRecordTypeConcrete: public CRecordType
{
public:
virtual int GetITable() const { return TRecord::k_iTable; }
virtual CRecordBase *Create() const { return new TRecord(); }
};
#define RTYPE( recordClass ) CRecordTypeConcrete<recordClass>()
} // namespace GCSDK
#include "tier0/memdbgoff.h"
#endif // GCRECORD_H
+174
View File
@@ -0,0 +1,174 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================
#ifndef GCRECORDINFO_H
#define GCRECORDINFO_H
namespace GCSDK
{
typedef CUtlMap<const char *,int> CMapIColumnInfo;
// --------------------------------------------------------------------------
// Information about a column in a record (table or result set)
class CColumnInfo
{
public:
CColumnInfo();
~CColumnInfo() { }
void Set( const char *pchName, int nSQLColumn, EGCSQLType eGCSQLType, int cubFixedSize, int nColFlags, int cubMaxSize );
const char *GetName() const { return m_rgchName; }
int GetSQLColumn() const { return m_nSQLColumn; }
EGCSQLType GetType() const { return m_eType; }
int GetFixedSize() const { return m_cubFixedSize; }
int GetMaxSize() const { return m_cchMaxSize; }
int GetChecksum() const { Assert( m_bHaveChecksum ); return m_nChecksum; }
bool BIsVariableLength() const;
int GetColFlags() const { return m_nColFlags; }
void GetColFlagDescription( char* pstrOut, int cubOutLength ) const;
int GetConstraintColFlags() { return m_nColFlags & k_nColFlagAllConstraints; }
void SetColFlagBits( int nColFlag );
bool BIsIndexed() const { return 0 != ( m_nColFlags & k_nColFlagIndexed ); }
bool BIsClustered() const { return 0 != ( m_nColFlags & k_nColFlagClustered ); }
bool BIsUnique() const { return 0 != ( m_nColFlags & k_nColFlagUnique ); }
bool BIsAutoIncrement() const { return 0 != ( m_nColFlags & k_nColFlagAutoIncrement ); }
bool BIsPrimaryKey() const { return 0 != ( m_nColFlags & k_nColFlagPrimaryKey ); }
bool BIsExplicitlyIndexed() const { return BIsIndexed() && !( BIsPrimaryKey() || BIsUnique() ); }
bool BIsExplicitlyUnique() const { return BIsUnique() && !BIsPrimaryKey(); }
bool BIsInsertable() const { return !BIsAutoIncrement(); }
void CalculateChecksum();
void ValidateColFlags() const;
bool operator==( const CColumnInfo& refOther ) const;
bool operator!=( const CColumnInfo& refOther ) const
{
return ! operator==( refOther );
}
#ifdef DBGFLAG_VALIDATE
void Validate( CValidator &validator, const char *pchName );
#endif // DBGFLAG_VALIDATE
private:
CColumnInfo( CColumnInfo& ); // no copy constructor, disable default copy constructor
CColumnInfo& operator = ( CColumnInfo& ); // no assignment operator, disable default assignment operator
char m_rgchName[k_cSQLObjectNameMax+1];
EGCSQLType m_eType; // GC-based enum data type of this column
int m_nColFlags; // flags for this column
int m_nSQLColumn; // column # in SQL database to bind to, starts at 1.
int m_cubFixedSize; // if fixed size, the fixed size in bytes; else 0
int m_cchMaxSize; // if variable size, the maximum size; else 0
int m_nChecksum; // checksum of this column info for quick comparisons
bool m_bHaveChecksum; // have we calculated a checksum yet?
};
// --------------------------------------------------------------------------
// Information about a record (table or result set)
class CRecordInfo : public CRefCount
{
public:
CRecordInfo();
void InitFromDSSchema( CSchema *pSchema );
void SetName( const char *pchName );
const char *GetName() const { return m_rgchName; }
void AddColumn( const char *pchName, int nSQLColumn, EGCSQLType eGCSQLType, int cubFixedSize, int nColFlags, int cubMaxSize );
void SetAllColumnsAdded() { m_bAllColumnsAdded = true; }
void PrepareForUse();
int GetFixedSize() const { return m_cubFixedSize; }
int GetNumColumns() const { return m_VecColumnInfo.Count(); }
const CColumnInfo &GetColumnInfo( uint32 unColumn ) const { return m_VecColumnInfo[unColumn]; }
CColumnInfo &GetColumnInfo( uint32 unColumn ) { return m_VecColumnInfo[unColumn]; }
bool BFindColumnByName( const char *pchName, int *piColumn );
bool BPreparedForUse() const { return m_bPreparedForUse; }
void EnsureCapacity( int cColumns ) { m_VecColumnInfo.EnsureCapacity( cColumns ); }
int GetChecksum();
ESchemaCatalog GetESchemaCatalog() const { return m_eSchemaCatalog; }
void SetESchemaCatalog( ESchemaCatalog e ) { m_eSchemaCatalog = e; }
bool EqualTo( CRecordInfo* pOther );
bool CompareIndexLists( CRecordInfo *pOther );
bool CompareFKs( CRecordInfo *pOther );
bool CompareFTSIndexLists( CRecordInfo *pOther ) const;
EPrimaryKeyType GetPrimaryKeyType() const { return m_nHasPrimaryKey; }
bool BHasPrimaryKey() { return GetPrimaryKeyType() != k_EPrimaryKeyTypeNone; }
const FieldSet_t& GetPKFields() { Assert( BHasPrimaryKey()); return GetIndexFields( )[ m_iPKIndex ]; }
const CUtlVector<FieldSet_t>& GetIndexFields() const { return m_VecIndexes; }
int GetIndexFieldCount() const { return m_VecIndexes.Count(); }
int FindIndex( CRecordInfo *pRec, const FieldSet_t& fieldSet );
int FindIndexByName( const char *pszName ) const;
int GetPKIndex() const { return m_iPKIndex; }
void SetPKIndex( int i ) { m_iPKIndex = i; }
int AddIndex( const FieldSet_t& fieldSet );
void GetIndexFieldList( CFmtStr1024 *pstr, int nIndents ) const;
int GetTableID() const { return m_nTableID; }
void SetTableID( int nTableID ) { m_nTableID = nTableID; }
bool BHasIdentity() const;
// full-text index
CUtlVector<int> & GetFTSFields() { return m_vecFTSFields; }
bool BHasFTSIndex() const { return m_vecFTSFields.Count() > 0; }
void AddFTSFields( CUtlVector< int > &refVecFields );
int GetFullTextCatalogIndex() { return m_nFullTextCatalogIndex; }
// foreign keys
void AddFK( const FKData_t &fkData );
void GetFKListString( CFmtStr1024 *pstr, int nIndents );
int GetFKCount();
FKData_t &GetFKData( int iIndex );
static CRecordInfo *Alloc();
#ifdef DBGFLAG_VALIDATE
static void ValidateStatics( CValidator &validator, const char *pchName );
void Validate( CValidator &validator, const char *pchName );
#endif //DBGFLAG_VALIDATE
// note: destructor is private. This is a ref-counted object, private destructor ensures callers can't accidentally delete
// directly, or declare on stack
virtual ~CRecordInfo() { }
private:
virtual void DestroyThis();
void CalculateChecksum();
void BuildColumnNameIndex();
char m_rgchName[k_cSQLObjectNameMax+1];
int m_nTableID; // Object_ID if this table in SQL Server
CUtlVector<CColumnInfo> m_VecColumnInfo; // Vector of columns in this record
CMapIColumnInfo m_MapIColumnInfo; // Map of name->column index for quick lookup by name
EPrimaryKeyType m_nHasPrimaryKey; // Does this table contain a column that is a primary key?
int m_iPKIndex; // index info m_VecIndexes of our PK index; -1 if no PK
CUtlVector<FieldSet_t> m_VecIndexes; // vector of all fields in all indexes
int m_cubFixedSize; // Sum of data sizes for all fixed size columns
bool m_bAllColumnsAdded; // Have all columns been added
bool m_bPreparedForUse; // Have we finished being initialized?
bool m_bHaveColumnNameIndex; // Have we created a column name index? (Only generated if someone asks.)
bool m_bHaveChecksum; // Have we generated a checksum? (Only generated if someone asks.)
int m_nChecksum; // checksum of this record info for quick comparisons - includes all columns
ESchemaCatalog m_eSchemaCatalog; // what catalog owns this object?
CUtlVector< int > m_vecFTSFields; // which fields have FTS indexing?
int m_nFullTextCatalogIndex; // index of catalog for FTS index, if we get one
CUtlVector<FKData_t> m_VecFKData; // vector of all FK relationships defined on this table
CThreadMutex m_Mutex;
static CThreadSafeClassMemoryPool<CRecordInfo> sm_MemPoolRecordInfo;
#ifdef _DEBUG
// validation tracking
static CUtlRBTree<CRecordInfo *, int > sm_mapPMemPoolRecordInfo;
static CThreadMutex sm_mutexMemPoolRecordInfo;
#endif
};
int __cdecl CompareColumnInfo( const CColumnInfo *pColumnInfoLeft, const CColumnInfo *pColumnInfoRight );
} // namespace GCSDK
#endif // GCRECORDINFO_H
+601
View File
@@ -0,0 +1,601 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================
#ifndef GCSCHEMA_H
#define GCSCHEMA_H
#ifdef _WIN32
#pragma once
#endif
namespace GCSDK
{
const int k_nColFlagIndexed = 0x0001; // this column is indexed.
const int k_nColFlagUnique = 0x0002; // this column has a uniqueness constraint - creates implicit index
const int k_nColFlagPrimaryKey = 0x0004; // this column has a primary key constraint - creates implicit uniqueness constraint & implicit index
const int k_nColFlagAutoIncrement = 0x0008; // this column can have it's values created implicitly by the sql counter
const int k_nColFlagClustered = 0x0010; // this column is clustered
const int k_nColFlagAllConstraints = k_nColFlagUnique | k_nColFlagPrimaryKey;
class CRecordInfo;
struct VarFieldBlockInfo_t;
struct VarFieldBlockInfo_t;
struct Field_t;
struct VarField_t;
// Function type for altering field types when converting schemas
typedef void (* PfnAlterField_t )( void *pvDest, const void *pvSrc );
// EPrimaryKeyType
// This shows if a table has a primary key and, if so, if it has multiple columns
// or not.
enum EPrimaryKeyType
{
k_EPrimaryKeyTypeNone = 0, // none at all
k_EPrimaryKeyTypeSingle, // single-column primary key
k_EPrimaryKeyTypeMulti, // multi-column primary key
};
// EWipePolicy
// This tells us if a table is supposed to be wiped before all tests,
// before all tests except stress tests, or not to be wiped before tests.
enum EWipePolicy
{
k_EWipePolicyPreserveAlways = 0, // don't wipe table
k_EWipePolicyPreserveForStress = 1, // preserve for stress tests, wipe before regular tests
k_EWipePolicyWipeForAllTests = 2, // wipe table prior to all tests
};
//-----------------------------------------------------------------------------
// Field
// This defines the schema of a single field in one of our tables.
//-----------------------------------------------------------------------------
struct Field_t
{
bool BGetIntData( uint8 *pubRecord, uint64 *pulRet ) const;
bool BGetFloatData( uint8 *pubRecord, float *fRet ) const;
bool SetIntData( uint8 *pubRecord, uint64 ulValue ) const;
// Not all fields are updated wholly at a time
int CubFieldUpdateSize() const;
// Handy helpers - complex fields like "rolling unique" are
// still binary
bool BIsStringType() const;
bool BIsVariableLength() const;
// Members that get serialized
EGCSQLType m_EType; // Field type
uint32 m_cubLength; // Length of the field in bytes
uint32 m_cchMaxLength; // maximum length of the field in characters
char m_rgchName[k_cSQLObjectNameMax]; // Human-readable name of this field
char m_rgchSQLName[k_cSQLObjectNameMax]; // SQL name of this field
// Members that don't get serialized
uint32 m_nColFlags; // k_nColFlag* bits for this field
uint32 m_dubOffset; // Offset of this field from beginning of record
};
// --------------------------------------------------------------------------
// Information about a foreign key relationship defined on a table
struct FKColumnRelation_t
{
char m_rgchCol[k_cSQLObjectNameMax+1];
char m_rgchParentCol[k_cSQLObjectNameMax+1];
FKColumnRelation_t()
{
Q_memset( m_rgchCol, 0, Q_ARRAYSIZE( m_rgchCol ) );
Q_memset( m_rgchParentCol, 0, Q_ARRAYSIZE( m_rgchParentCol ) );
}
bool operator==( const FKColumnRelation_t &other ) const
{
if ( Q_stricmp( m_rgchCol, other.m_rgchCol ) )
return false;
if ( Q_stricmp( m_rgchParentCol, other.m_rgchParentCol ) )
return false;
return true;
}
};
struct FKData_t
{
char m_rgchName[k_cSQLObjectNameMax+1];
char m_rgchParentTableName[k_cSQLObjectNameMax+1];
CCopyableUtlVector<FKColumnRelation_t> m_VecColumnRelations;
EForeignKeyAction m_eOnDeleteAction;
EForeignKeyAction m_eOnUpdateAction;
FKData_t()
{
Q_memset( m_rgchName, 0, Q_ARRAYSIZE( m_rgchName ) );
Q_memset( m_rgchParentTableName, 0, Q_ARRAYSIZE( m_rgchParentTableName ) );
m_eOnDeleteAction = k_EForeignKeyActionNoAction;
m_eOnUpdateAction = k_EForeignKeyActionNoAction;
}
bool operator==( const FKData_t &other ) const
{
if ( Q_stricmp( m_rgchName, other.m_rgchName ) )
return false;
if ( Q_stricmp( m_rgchParentTableName, other.m_rgchParentTableName ) )
return false;
if ( m_eOnDeleteAction != other.m_eOnDeleteAction || m_eOnUpdateAction != other.m_eOnUpdateAction )
return false;
FOR_EACH_VEC( m_VecColumnRelations, i )
{
bool bFoundInOther = false;
const FKColumnRelation_t &cols = m_VecColumnRelations[i];
FOR_EACH_VEC( other.m_VecColumnRelations, j )
{
const FKColumnRelation_t &colsOther = other.m_VecColumnRelations[j];
if ( cols == colsOther )
{
bFoundInOther = true;
break;
}
}
if ( !bFoundInOther )
return false;
}
return true;
}
#ifdef DBGFLAG_VALIDATE
// Validate our internal structures
void Validate( CValidator &validator, const char *pchName )
{
VALIDATE_SCOPE();
ValidateObj( m_VecColumnRelations );
}
#endif
};
#pragma pack( push, 1 )
//-----------------------------------------------------------------------------
// SerSchema
// Defines the binary serialization format for a schema.
//-----------------------------------------------------------------------------
struct SerSchema_t
{
uint32 m_cub; // Size of the whole schema (including header and fields)
int32 m_iTable; // Our table's iTable
char m_rgchName[k_cSQLObjectNameMax]; // Human-readable name of this table
int16 m_cField; // # of fields in the schema (int16 for backward-compatibility reasons)
int16 m_ETableGroup; // Our table's TableGroup (int16 for backward-compatibility reasons) - OBSOLETE
};
//-----------------------------------------------------------------------------
// SerField
// Defines the binary serialization format for a field in a schema
// Note that certain fields are missing from this because we only use serialized
// schemas for schema mapping. Fields that only affect runtime behavior (like
// indexing) are always defined by the intrinsic schema.
//-----------------------------------------------------------------------------
struct SerField_t
{
int32 m_EType; // Field type
uint32 m_cubLength; // Length of field data in bytes
// For rolling fields, high 16 bits are the
// size of each element
char m_rgchName[k_cSQLObjectNameMax];// Human-readable name of this field
char m_rgchSQLName[k_cSQLObjectNameMax]; // SQL name of this field
};
#pragma pack( pop )
//-----------------------------------------------------------------------------
// Schema conversion instructions
// These specify various operations that can be performed when converting
// from one Schema to another.
//-----------------------------------------------------------------------------
struct DeleteField_t
{
char m_rgchFieldName[k_cSQLObjectNameMax]; // Name of the field to delete
};
struct RenameField_t
{
char m_rgchFieldNameOld[k_cSQLObjectNameMax]; // Rename a field with this name
int m_iFieldDst; // to this field
};
struct AlterField_t
{
char m_rgchFieldNameOld[k_cSQLObjectNameMax]; // Name of field in the old schema
int m_iFieldDst; // iField of it in the new
PfnAlterField_t m_pfnAlterFunc; // Function to translate the data
};
//-----------------------------------------------------------------------------
// FieldSet_t describes a collection of fields in an index, as well as
// attributes of the index itself
//-----------------------------------------------------------------------------
class FieldSet_t
{
public:
FieldSet_t( bool bUnique, bool bClustered, const CUtlVector<int>& vecFields, const char* pstrIndexName )
: m_bClustered( bClustered ), m_bUnique( bUnique )
{
m_VecFields = vecFields;
// zero means to use the server default
m_nFillFactor = 0;
// null name is allowed for primary keys
if ( pstrIndexName != NULL )
Q_strncpy( m_szIndexName, pstrIndexName, Q_ARRAYSIZE( m_szIndexName ) );
else
m_szIndexName[0] = 0;
}
FieldSet_t( )
{
}
~FieldSet_t( )
{
}
FieldSet_t( const FieldSet_t &refOther )
{
DoAssignment( refOther );
}
FieldSet_t& operator=( const FieldSet_t &refOther )
{
DoAssignment( refOther );
return *this;
}
void DoAssignment( const FieldSet_t &refOther )
{
m_VecFields = refOther.m_VecFields;
m_VecIncluded = refOther.m_VecIncluded;
m_bClustered = refOther.m_bClustered;
m_bUnique = refOther.m_bUnique;
m_nFillFactor = refOther.m_nFillFactor;
Q_strncpy( m_szIndexName, refOther.m_szIndexName, Q_ARRAYSIZE( m_szIndexName ) );
}
// get count of fields in this index
int GetCount() const
{
return m_VecFields.Count();
}
// get count of included fields in this index
int GetIncludedCount() const
{
return m_VecIncluded.Count();
}
void AddIncludedColumn( int nIndex )
{
m_VecIncluded.AddToTail( nIndex );
}
void AddIncludedColumns( const CUtlVector<int> &refVec )
{
m_VecIncluded.AddVectorToTail( refVec );
}
// get a particular field ID
// the returned index is into the VecFields of the associated schema
int GetField( int nIndex ) const
{
return m_VecFields[ nIndex ];
}
int GetIncludedField( int nIndex ) const
{
return m_VecIncluded[ nIndex ];
}
// is this index clustered?
bool IsClustered() const
{
return m_bClustered;
}
// is this index unique?
bool IsUnique() const
{
return m_bUnique;
}
void SetClustered( bool bIsClustered )
{
m_bClustered = bIsClustered;
}
void SetFillFactor( int nFactor )
{
Assert( nFactor >= 0 && nFactor <= 100 );
m_nFillFactor = nFactor;
}
int GetFillFactor( ) const
{
return m_nFillFactor;
}
const char* GetIndexName() const
{
return m_szIndexName;
}
// determine if this fieldset is equal to the other one
static bool CompareFieldSets( const FieldSet_t& refThis, CRecordInfo* pRecordInfoThis,
const FieldSet_t& refOther, CRecordInfo* pRecordInfoOther );
#ifdef DBGFLAG_VALIDATE
// Validate our internal structures
void Validate( CValidator &validator, const char *pchName )
{
VALIDATE_SCOPE();
m_VecFields.Validate( validator, "m_VecFields" );
m_VecIncluded.Validate( validator, "m_VecIncluded" );
}
#endif
private:
CUtlVector<int> m_VecFields; // ids of fields; indexes into m_VecFields of CSchema for a table
CUtlVector<int> m_VecIncluded; // ids of included fields
int m_nFillFactor; // fill factor for the index; zero means to use the server's default
char m_szIndexName[k_cSQLObjectNameMax]; // name of this index
bool m_bClustered:1; // is this index clustered?
bool m_bUnique:1; // is this index unique?
};
//-----------------------------------------------------------------------------
enum ESchemaCatalog
{
k_ESchemaCatalogInvalid = -1,
k_ESchemaCatalogMain = 0, // main GC catalog
k_ESchemaCatalogOGS = 4, // operational game stats
};
extern const char* PchNameFromESchemaCatalog( ESchemaCatalog e );
//-----------------------------------------------------------------------------
// CSchema
// This defines the schema for a single table. The schema essentially defines
// what's in the table (ie, field 0 is a 32 char string called "Name", etc.)
// The schema is in charge of manipulating individual records within the table.
//-----------------------------------------------------------------------------
class CSchemaFull;
class CSchema
{
public:
// Constructors & destructors
CSchema();
~CSchema();
// Recalculates field offsets and maximum record size.
// Must be called after changing schema.
void CalcOffsets();
// called to make final calculations when all fields/indexes/etc have been added and the schema is ready to be used
void PrepareForUse();
// Sizing information
uint32 CubSerialSchema() const { return( sizeof( SerSchema_t ) + m_VecField.Count() * sizeof( SerField_t ) ); }
// Size of total fixed-length portion of record
uint32 CubRecordFixed() const { return( m_cubRecord ); }
// Size of the total variable-length portion of record (zero if no var-length fields)
uint32 CubRecordVariable( const void *pvRecord ) const;
// Does this record have variable-length fields?
bool BHasVariableFields() const { return m_bHasVarFields; }
VarFieldBlockInfo_t* PVarFieldBlockInfoFromRecord( const void *pvRecord ) const;
// Access field data - fixed or variable (may return NULL/0 for empty var field)
bool BGetFieldData( const void *pvRecord, int iField, uint8 **ppubField, uint32 *pcubField ) const;
bool BSetFieldData( void *pvRecord, int iField, uint8 *pubField, uint32 cubField, bool *pbVarBlockRealloced );
bool BGetVarField( const void *pvRecord, const VarField_t *pVarField, uint8 **ppubField, uint32 *pcubField ) const;
bool BSetVarField( void *pvRecord, VarField_t *pVarField, const void *pvData, uint32 cubData, bool *pbRealloced, bool bFreeOnRealloc );
// Adjust var-block pointer, if present, to point just after the fixed part of the record
void FixupDeserializedRecord( void *pvRecord );
// Render a record in text format
void RenderRecord( uint8 *pubRecord );
void RenderField( uint8 *pubRecord, int iField, int cchBuffer, char *pchBuffer );
// Accessors
void SetITable( int iTable ) { m_iTable = iTable; }
int GetITable() const { return m_iTable; }
int GetCField() const { return m_VecField.Count(); }
void SetReportingInterval( int nInterval ) { m_nReportingInterval = nInterval; }
int GetReportingInterval( ) const { return m_nReportingInterval; }
Field_t &GetField( int iField ) { return m_VecField[iField]; }
const Field_t &GetField( int iField ) const { return m_VecField[iField]; }
VarField_t *GetPVarField( void *pvRecord, int iField ) { return ( VarField_t * )( ( uint8 * ) pvRecord + m_VecField[iField].m_dubOffset ); }
void SetName( const char *pchName ) { Q_strncpy( m_rgchName, pchName, sizeof( m_rgchName ) ); }
const char *GetPchName() const { return m_rgchName; }
const FieldSet_t& GetPKFields() { Assert( m_iPKIndex != -1 ); return GetIndexes()[m_iPKIndex]; }
int GetPKIndex() const { return m_iPKIndex; }
const CUtlVector<FieldSet_t>& GetIndexes() { return m_VecIndexes; }
const CUtlVector<int>& GetFTSColumns() { return m_VecFullTextIndexes; }
int GetFTSIndexCatalog() const { return m_nFullTextIndexCatalog; }
ESchemaCatalog GetESchemaCatalog() const { return m_eSchemaCatalog; }
void SetESchemaCatalog( ESchemaCatalog eSchemaCatalog ) { m_eSchemaCatalog = eSchemaCatalog; }
// If cRecordMax is non-zero, this is a rolling table that only
// holds on to cRecordMax records at most.
void SetCRecordMax( int cRecordMax ) { m_cRecordMax = cRecordMax; }
int GetCRecordMax() const { return m_cRecordMax; }
// Is this table for TESTs only?
void SetBTestTable( bool bTestTable ) { m_bTestTable = bTestTable; }
bool GetBTestTable() const { return m_bTestTable; }
// Randomly init a record or field to random values
void InitRecordRandom( uint8 *pubRecord, uint32 unPrimaryIndex, bool *pbVarBlockRealloced, bool bFreeVarBlockOnRealloc );
void SetFieldRandom( uint8 *pubRecord, int iField, bool *pbVarBlockRealloced, bool bFreeVarBlockOnRealloc );
// Checksum the schema
uint32 CalcChecksum();
// pre-allocate space in the field array
void EnsureFieldCount( int cFields )
{
m_VecField.EnsureCapacity( cFields );
}
// This adds a field from our intrinsic schema to us
void AddField( char *pchName, char *pchSQLName, EGCSQLType eType, uint32 cubSize, int cchMaxLength );
void AddIntField( char *pchName, char *pchSQLName, EGCSQLType eType, int cubSize );
// We want to make a particular field the primary key
int PrimaryKey( bool bClustered, int nFillFactor, const char *pchName );
// we want to make a particular list of fields the primary key
int PrimaryKeys( bool bClustered, int nFillFactor, const char *pchNames );
// We want to index a particular field by name
int IndexField( const char *pchName, const char *pchIndexName );
// We want to index a particular list of fields in a group
int IndexFields( const char *pchIndexName, const char *pchNames );
// We want a certain index to additionally include a list of fields
void AddIncludedFields( const char *pchIndexName, const char *pchNames );
// We want to unique index a particular list of fields in a group
int UniqueFields( const char *pchIndexName, const char *pchNames );
// add a full-text index to the given column
void AddFullTextIndex( CSchemaFull *pSchemaFull, const char *pchCatalogName, const char *pchColumnName );
// We want to index a particular field by field number
// (field number is an index into the m_VecField array)
int AddIndexToFieldNumber( int iField, const char *pchIndexName, bool bClustered );
// We want to index a particular set of fields
// pchNames includes the names, separated by commas, of each field
int AddIndexToFieldList( const char *pchNames, const char *pchIndexName, int nFlags, int nFillFactor );
// We want a unique index on a particular field
int UniqueField( const char *pchName, const char *pchIndexName );
// We want to have a clustered index on a particular field by name
int ClusteredIndexField( int nFillFactor, const char *pchName, const char *pchIndexName );
// We want to index a particular list of fields in a group
int ClusteredIndexFields( int nFillFactor, const char *pchIndexName, const char *pchNames );
// We want an autoinc on a particular field
void AutoIncrementField( char *pchName );
// catalog on which we'll enable FTS
void EnableFTS( ESchemaCatalog eCatalog );
// adds a full text catalog with the given name on the identified fileset
void AddFullTextCatalog( ESchemaCatalog eCatalog, const char *pstrCatalogName, const char *pstrFileGroupName );
// Adds a FK on the table
void AddFK( const char* pchName, const char* pchColumn, const char* pchParentTable, const char* pchParentColumn, EForeignKeyAction eOnDeleteAction, EForeignKeyAction eOnUpdateAction );
// Access FK data
int GetFKCount();
FKData_t &GetFKData( int iIndex );
void SetTestWipePolicy( EWipePolicy policy ) { m_wipePolicy = policy; }
EWipePolicy GetTestWipePolicy() const { return m_wipePolicy; }
void SetBAllowWipeTableInProd( bool bVal ) { m_bAllowWipeInProd = bVal; }
bool BAllowWipeTableInProd() const { return m_bAllowWipeInProd; }
void SetPrepopulatedTable( ) { m_bPrepopulatedTable = true; }
bool BPrepopulatedTable( ) const { return m_bPrepopulatedTable; }
// Find the field with a given name (returns k_iFieldNil if not found)
int FindIField( const char *pchName );
int FindIFieldSQL( const char *pchName );
// Helper functions for recording schema conversion operations
void AddDeleteField( const char *pchFieldName );
void AddRenameField( const char *pchFieldNameOld, const char *pchFieldNameNew );
void AddAlterField( const char *pchFieldNameOld, const char *pchFieldNameNew, PfnAlterField_t pfnAlterField );
// Schema conversion helper: figure out what field to map a field from a different schema to
bool BCanConvertField( const char *pchFieldSrc, int *piFieldDst, PfnAlterField_t *ppfnAlterField );
CRecordInfo *GetRecordInfo() { return m_pRecordInfo; }
const CRecordInfo *GetRecordInfo() const { return m_pRecordInfo; }
void Validate( CValidator &validator, const char *pchName ); // Validate our internal structures
void ValidateRecord( uint8 *pubRecord ); // Validate a record that uses our schema
// cached queries
const char *GetInsertStatementText() const;
const char *GetMergeStatementTextOnPKWhenMatchedUpdateWhenNotMatchedInsert();
const char *GetMergeStatementTextOnPKWhenNotMatchedInsert();
private:
int m_iTable; // The index of our table
int m_iPKIndex; // index into of m_VecIndexes of our PK index; k_iFieldNil if no PK
char m_rgchName[k_cSQLObjectNameMax]; // Name of this table
CUtlVector<Field_t> m_VecField; // All the fields that make up the schema
CUtlVector<FieldSet_t> m_VecIndexes; // vector of all fields in all indexes
int m_cRecordMax; // Max # records in the table (for rolling tables)
bool m_bTestTable; // Table exists only for tests
bool m_bAllowWipeInProd; // should we allow WipeTable operations on this table in the beta/public universe?
EWipePolicy m_wipePolicy; // should this table be wiped between all tests, no tests, or non-stress tests?
bool m_bHasVarFields; // True if this table has variable-length fields
bool m_bPrepopulatedTable; // true if this table is pre-populated
EPrimaryKeyType m_nHasPrimaryKey; // what kind of PK do we have, if any?
CRecordInfo *m_pRecordInfo; // The record description corresponding to this schema. (Similar info, record description is new form, have both for a while during DS->SQL switch)
CUtlVector<int> m_VecFullTextIndexes; // vector of indexes into m_VecField of fields covered by this table's full-text index.
int m_nFullTextIndexCatalog; // index of catalog to use for creating full-text indexes
CUtlVector<FKData_t> m_VecFKData; // data on foreign keys for this schema object
uint32 m_cubRecord; // Binary record length
int m_nReportingInterval; // reporting interval of this table if stats; 0 if not stats
ESchemaCatalog m_eSchemaCatalog; // what catalog does this table live in?
// Schema conversion instructions
CUtlVector<DeleteField_t> m_VecDeleteField;
CUtlVector<RenameField_t> m_VecRenameField;
CUtlVector<AlterField_t> m_VecAlterField;
// Cached queries
mutable CUtlString m_sInsertStatementText; // Cached insert statement for the table
CUtlString m_sMergeStatementTextOnPKWhenMatchedUpdateWhenNotMatchedInsert; // Cached insert or update via MERGE statement for the table
CUtlString m_sMergeStatementTextOnPKWhenNotMatchedInsert; // Cached insert via MERGE statement for the table
};
} // namespace GCSDK
#endif // GCSCHEMA_H
+284
View File
@@ -0,0 +1,284 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================
#ifndef GCSCHEMAFULL_H
#define GCSCHEMAFULL_H
#ifdef _WIN32
#pragma once
#endif
namespace GCSDK
{
//-----------------------------------------------------------------------------
// SerSchemaFull
// This defines the binary serialization format for a CSchemaFull
//-----------------------------------------------------------------------------
struct SerSchemaFull_t
{
enum EVersion
{
k_ECurrentVersion = 1,
};
int32 m_nVersion; // version of serialization format
int32 m_cSchema; // # of schema we contain
};
//-----------------------------------------------------------------------------
// CFTSCatalogInfo
// information about a full text search catalog object in our schema
//-----------------------------------------------------------------------------
class CFTSCatalogInfo
{
public:
enum ESchemaCatalog m_eCatalog;
const char *m_pstrName;
int m_nFileGroup;
CFTSCatalogInfo()
: m_pstrName( NULL ),
m_eCatalog( k_ESchemaCatalogInvalid )
{
}
~CFTSCatalogInfo()
{
free( (void*) m_pstrName);
}
CFTSCatalogInfo( const CFTSCatalogInfo &refOther )
{
m_eCatalog = refOther.m_eCatalog;
m_nFileGroup = refOther.m_nFileGroup;
if ( refOther.m_pstrName != NULL )
m_pstrName = strdup( refOther.m_pstrName );
else
m_pstrName = NULL;
}
#ifdef DBGFLAG_VALIDATE
void Validate( CValidator &validator, const char *pchName ) // Validate our internal structures
{
validator.ClaimMemory( (void *) m_pstrName );
}
#endif
};
//-----------------------------------------------------------------------------
// SchemaFull conversion instructions
// These specify various operations that can be performed when converting
// from one SchemaFull to another.
//-----------------------------------------------------------------------------
struct DeleteTable_t
{
char m_rgchTableName[k_cSQLObjectNameMax]; // Name of the table to delete
};
struct RenameTable_t
{
char m_rgchTableNameOld[k_cSQLObjectNameMax]; // Rename a table with this name
int m_iTableDst; // to this table
};
enum ETriggerType
{
k_ETriggerType_Invalid,
k_ETriggerType_After_Insert,
k_ETriggerType_InsteadOf_Insert,
k_ETriggerType_After_Delete,
k_ETriggerType_InsteadOf_Delete,
k_ETriggerType_After_Update,
k_ETriggerType_InsteadOf_Update,
};
class CTriggerInfo
{
public:
CTriggerInfo()
: m_eTriggerType( k_ETriggerType_Invalid ),
m_bMatched( false )
{
}
// are these equal for identity?
bool operator==( const CTriggerInfo& refOther ) const
{
if ( 0 != Q_stricmp( m_szTriggerTableName, refOther.m_szTriggerTableName ) )
return false;
if ( 0 != Q_stricmp( m_szTriggerName, refOther.m_szTriggerName ) )
return false;
// they're equal!
return true;
}
// if the identity is the same, this will tell if text or type differs
bool IsDifferent( const CTriggerInfo& refOther ) const
{
if ( m_eTriggerType != refOther.m_eTriggerType )
return false;
if ( m_strText != refOther.m_strText )
return false;
// they're equal!
return true;
}
const char* GetTriggerTypeString() const
{
const char *pstrSQL = "~~ unknown trigger type syntax error ~~";
switch ( m_eTriggerType )
{
case k_ETriggerType_After_Insert:
pstrSQL = "AFTER INSERT";
break;
case k_ETriggerType_InsteadOf_Insert:
pstrSQL = "INSTEAD OF INSERT";
break;
case k_ETriggerType_After_Delete:
pstrSQL = "AFTER DELETE";
break;
case k_ETriggerType_InsteadOf_Delete:
pstrSQL = "INSTEAD OF DELETE";
break;
case k_ETriggerType_After_Update:
pstrSQL = "AFTER UPDATE";
break;
case k_ETriggerType_InsteadOf_Update:
pstrSQL = "INSTEAD OF UPDATE";
break;
default:
case k_ETriggerType_Invalid:
/* initialize is fine, thanks */
break;
}
return pstrSQL;
}
bool m_bMatched; // got matched during schema convert
ETriggerType m_eTriggerType; // what kinda trigger is this?
ESchemaCatalog m_eSchemaCatalog; // catalog where this trigger lives
char m_szTriggerName[k_cSQLObjectNameMax]; // name of the trigger object
char m_szTriggerTableName[k_cSQLObjectNameMax]; // name of the table hosting this trigger
CUtlString m_strText; // text of the trigger
// Validate our internal structures
#ifdef DBGFLAG_VALIDATE
void Validate( CValidator &validator, const char *pchName )
{
m_strText.Validate( validator, pchName );
}
#endif
};
//-----------------------------------------------------------------------------
// CSchemaFull
// This defines the schema for the entire data store. It's essentially just
// a collection of CSchema, which define the schema for individual tables.
//-----------------------------------------------------------------------------
class CSchemaFull
{
public:
// Constructors & destructors
CSchemaFull();
~CSchemaFull();
void Uninit();
// add a new schema and return its pointer.
CSchema *AddNewSchema( int iTable, ESchemaCatalog eCatalog, const char *pstrName )
{
CSchema &refNewSchema = m_VecSchema[m_VecSchema.AddToTail()];
refNewSchema.SetName( pstrName );
refNewSchema.SetESchemaCatalog( eCatalog );
SetITable( &refNewSchema, iTable );
return &refNewSchema;
}
// Accessors
int GetCSchema() const { return m_VecSchema.Count(); }
CSchema &GetSchema( int iSchema ) { return m_VecSchema[iSchema]; }
uint32 GetCheckSum() const { return m_unCheckSum; }
const char *GetDefaultSchemaNameForCatalog( ESchemaCatalog eCatalog );
uint8 *GetPubScratchBuffer( );
uint32 GetCubScratchBuffer() const { return m_cubScratchBuffer; }
// Makes sure that a generated intrinsic schema is consistent
void CheckSchema( CSchema *pSchema, int cField, uint32 cubRecord );
// Find the table with a given name (returns -1 if not found)
int FindITable( const char *pchName );
const char *PchTableFromITable( int iTable );
// Helper functions for recording schema conversion operations
void AddDeleteTable( const char *pchTableName );
void AddRenameTable( const char *pchTableNameOld, const char *pchTableNameNew );
void AddDeleteField( const char *pchTableName, const char *pchFieldName );
void AddRenameField( const char *pchTableName, const char *pchFieldNameOld, const char *pchFieldNameNew );
void AddAlterField( const char *pchTableName, const char *pchFieldNameOld, const char *pchFieldNameNew, PfnAlterField_t pfnAlterField );
// declare that a trigger is on a table
void AddTrigger( ESchemaCatalog eCatalog, const char *pchTableName, const char *pchTriggerName, ETriggerType eTriggerType, const char *pchTriggerText );
// Schema conversion helper: figure out what table to map a table from a different schema to
bool BCanConvertTable( const char *pchTableSrc, int *piTableDst );
// full text catalogs
void AddFullTextCatalog( enum ESchemaCatalog eCatalog, const char *pstrCatalogName, int nFileGroup );
int GetFTSCatalogByName( enum ESchemaCatalog eCatalog, const char *pstrCatalogName );
void EnableFTS( enum ESchemaCatalog eCatalog );
int GetCFTSCatalogs() const { return m_vecFTSCatalogs.Count(); }
const CFTSCatalogInfo & GetFTSCatalogInfo( int nIndex ) const { return m_vecFTSCatalogs[nIndex]; }
const CUtlVector< CTriggerInfo> & GetTriggerInfos( ) const { return m_VecTriggers; }
// is the given schema catalog FTS enabled?
bool GetFTSEnabled( enum ESchemaCatalog eCatalog );
void Validate( CValidator &validator, const char *pchName ); // Validate our internal structures
// sets tableID on CSchema, checking that it is not a duplicate
void SetITable( CSchema* pSchema, int iTable );
void FinishInit(); // Recalculates some internal fields
private:
CUtlConstString m_strDefaultSchemaName;
CUtlVector< CSchema > m_VecSchema; // Schema for tables in all catalogs
CUtlVector< CTriggerInfo > m_VecTriggers; // list of triggers in all catalogs
// which schema catalogs have FTS enabled?
CUtlMap< ESchemaCatalog, bool > m_mapFTSEnabled;
// list of catalogs; each is marked with the schema where it lives.
CUtlVector< CFTSCatalogInfo > m_vecFTSCatalogs;
uint32 m_unCheckSum; // A simple checksum of our contents
// SchemaFull conversion instructions
CUtlVector<DeleteTable_t> m_VecDeleteTable;
CUtlVector<RenameTable_t> m_VecRenameTable;
uint8 *m_pubScratchBuffer; // Big enough to hold any record or sparse record in this schemafull
uint32 m_cubScratchBuffer; // Size of the scratch buffer
};
extern CSchemaFull & GSchemaFull();
} // namespace GCSDK
#endif // GCSCHEMAFULL_H
+95
View File
@@ -0,0 +1,95 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Contains the job that's responsible for updating the database schema
//
//=============================================================================
#ifndef UPDATESCHEMA_H
#define UPDATESCHEMA_H
#ifdef _WIN32
#pragma once
#endif
namespace GCSDK
{
typedef CUtlMap<const char *,CRecordInfo *> CMapPRecordInfo;
enum EConversionMode
{
k_EConversionModeInspectOnly,
k_EConversionModeConvertSafe,
k_EConversionModeConvertIrreversible
};
class CSchemaUpdate : public CRefCount
{
public:
CSchemaUpdate();
void AddRecordInfoDesired( CRecordInfo *pRecordInfo );
void AddFTSInfo( const CFTSCatalogInfo &refFTSInfo );
void AddTriggerInfos( const CUtlVector< CTriggerInfo > &refTriggerInfo );
// input parameters
CMapPRecordInfo m_mapPRecordInfoDesired;
EConversionMode m_eConversionMode;
CUtlLinkedList< CFTSCatalogInfo > m_listFTSCatalogInfo;
CUtlVector< CTriggerInfo > m_vecTriggerInfo;
// output parameters
bool m_bConversionNeeded;
bool m_bSkippedAChange;
int m_cTablesDesiredMissing;
int m_cTablesActualDifferent;
int m_cTablesActualUnknown;
int m_cTablesNeedingChange;
int m_cColumnsDesiredMissing;
int m_cColumnsActualDifferent;
int m_cColumnsActualUnknown;
CFmtStr1024 m_sDetail;
private:
virtual ~CSchemaUpdate();
};
// --------------------------------------------------------------------------
class CJobUpdateSchema : public CGCJob
{
public:
CJobUpdateSchema( CGCBase *pGC, int iTableCount ) : CGCJob( pGC ), m_mapSQLTypeToEType( DefLessFunc(int) ), m_iTableCount( iTableCount ) { }
bool BYieldingRunJob( void * );
private:
bool BYieldingUpdateSchema( ESchemaCatalog eSchemaCatalog );
SQLRETURN YieldingEnsureDatabaseSchemaCorrect( ESchemaCatalog eSchemaCatalog, CSchemaUpdate *pSchemaUpdate );
EGCSQLType GetEGCSQLTypeForMSSQLType( int nType );
bool YieldingBuildTypeMap( ESchemaCatalog eSchemaCatalog );
SQLRETURN YieldingGetSchemaID( ESchemaCatalog eSchemaCatalog, int *pSchemaID );
SQLRETURN YieldingGetRecordInfoForAllTables( ESchemaCatalog eSchemaCatalog, int nSchemaID, CMapPRecordInfo &mapPRecordInfo );
SQLRETURN YieldingGetColumnInfoForTable( ESchemaCatalog eSchemaCatalog, CMapPRecordInfo &mapPRecordInfo, int nTableID, const char *pchTableName );
SQLRETURN YieldingGetTableFKConstraints( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo );
SQLRETURN YieldingGetColumnIndexes( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo );
SQLRETURN YieldingGetTriggers( ESchemaCatalog eSchemaCatalog, int nSchemaID, CUtlVector< CTriggerInfo > &vecTriggerInfo );
SQLRETURN YieldingCreateTable( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo );
SQLRETURN YieldingAddIndex( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo, const FieldSet_t &refFields );
SQLRETURN YieldingAlterTableAddColumn( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfo );
SQLRETURN YieldingAddConstraint( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfo, int nColFlagConstraint );
SQLRETURN YieldingChangeColumnTypeOrLength( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfoDesired );
SQLRETURN YieldingChangeColumnProperties( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfoActual, const CColumnInfo *pColumnInfoDesired );
SQLRETURN YieldingCreateTrigger( ESchemaCatalog eSchemaCatalog, CTriggerInfo &refTriggerInfo );
SQLRETURN YieldingDropTrigger( ESchemaCatalog eSchemaCatalog, CTriggerInfo &refTriggerInfo );
CUtlMap<int,EGCSQLType> m_mapSQLTypeToEType;
int m_iTableCount;
EConversionMode m_eConversionMode;
CUtlString m_sRecommendedSQL;
CUtlString m_sDataDestroyingCleanupSQL;
void AddDataDestroyingConversion( const char *pszSQL, const char *pszComment );
SQLRETURN YieldingProcessUnsafeConversion( ESchemaCatalog eSchemaCatalog, const char *pszSQL, const char *pszComment = NULL );
};
} // namespace GCSDK
#endif // UPDATESCHEMA_H
+290
View File
@@ -0,0 +1,290 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Provides access to SQL at a high level
//
//=============================================================================
#ifndef SQLACCESS_H
#define SQLACCESS_H
#ifdef _WIN32
#pragma once
#endif
#include "gcsdk/gcsqlquery.h"
#include <functional>
#include "tier0/memdbgon.h"
namespace GCSDK
{
class CGCSQLQuery;
class CGCSQLQueryGroup;
class CColumnSet;
class CRecordType;
//-----------------------------------------------------------------------------
// Purpose: Provides access to SQL at a high level
//-----------------------------------------------------------------------------
class CSQLAccess
{
public:
CSQLAccess( ESchemaCatalog eSchemaCatalog = k_ESchemaCatalogMain );
~CSQLAccess( );
bool BBeginTransaction( const char *pchName );
MUST_CHECK_RETURN bool BCommitTransaction( bool bAllowEmpty = false );
void RollbackTransaction();
bool BInTransaction( ) const { return m_bInTransaction; }
const char *PchTransactionName( ) const;
// Add a listener to be executed should this transaction successfully complete. Invalid to call if
// !BInTransaction()
//
// NOTE: Listeners must assume they are within a transaction commit scope with relevant locks, and must not yield or
// perform re-entrant actions.
void AddCommitListener( std::function<void (void)> &&listener );
// Add a listener to be executed should this transaction be rolled back, explicitly or automatically. Invalid to
// call if !BInTransaction()
//
// NOTE: Listeners must assume they are within a transaction commit scope with relevant locks, and must not yield or
// perform re-entrant actions.
void AddRollbackListener( std::function<void (void)> &&listener );
bool BYieldingExecute( const char *pchName, const char *pchSQLCommand, uint32 *pcRowsAffected = NULL, bool bSpewOnError = true );
bool BYieldingExecuteString( const char *pchName, const char *pchSQLCommand, CFmtStr1024 *psResult, uint32 *pcRowsAffected = NULL );
bool BYieldingExecuteScalarInt( const char *pchName, const char *pchSQLCommand, int *pnResult, uint32 *pcRowsAffected = NULL );
bool BYieldingExecuteScalarIntWithDefault( const char *pchName, const char *pchSQLCommand, int *pnResult, int iDefaultValue, uint32 *pcRowsAffected = NULL );
bool BYieldingExecuteScalarUint32( const char *pchName, const char *pchSQLCommand, uint32 *punResult, uint32 *pcRowsAffected = NULL );
bool BYieldingExecuteScalarUint32WithDefault( const char *pchName, const char *pchSQLCommand, uint32 *punResult, uint32 unDefaultValue, uint32 *pcRowsAffected = NULL );
bool BYieldingWipeTable( int iTable );
template <typename TReturn, typename TCast>
bool BYieldingExecuteSingleResult( const char *pchName, const char *pchSQLCommand, EGCSQLType eType, TReturn *pResult, uint32 *pcRowsAffected );
template <typename TReturn, typename TCast>
bool BYieldingExecuteSingleResultWithDefault( const char *pchName, const char *pchSQLCommand, EGCSQLType eType, TReturn *pResult, TReturn defaultValue, uint32 *pcRowsAffected );
// manipulating CRecordBase (i.e. CSch...) objects in the database
bool BYieldingInsertRecord( const CRecordBase *pRecordBase );
bool BYieldingInsertWhenNotMatchedOnPK( CRecordBase *pRecordBase );
bool BYieldingInsertOrUpdateOnPK( CRecordBase *pRecordBase );
bool BYieldingInsertWithIdentity( CRecordBase* pRecordBase );
/// Locate a *single* record from the table which matches the values of the specified columns.
/// Returns:
/// - k_EResultOK - A single record was found and returned
/// - k_EResultNoMatch - no records found
/// - k_EResultLimitExceeded - More than one record found (false is returned in this case --- use a different function to do the query)
/// - (Other K_EResult codes might be returned for invalid usage, SQL problem, etc)
/// If no order by clause is provided, this assumes a singleton will be returned, and will fail if more than one record meets the criteria, if an order by
/// clause is provided though, this will just return the first element
EResult YieldingReadRecordWithWhereColumns( CRecordBase *pRecord, const CColumnSet & readSet, const CColumnSet & whereSet, const char* pchOrderClause = NULL );
template< typename SchClass_t>
bool BYieldingReadRecordsWithWhereClause( CUtlVector< SchClass_t > *pvecRecords, const char *pchWhereClause, const CColumnSet & readSet, const char *pchTopClause = NULL, const char* pchOrderClause = NULL );
template< typename SchClass_t>
bool BYieldingReadRecordsWithQuery( CUtlVector< SchClass_t > *pvecRecords, const char *sQuery, const CColumnSet & readSet );
bool BYieldingUpdateRecord( const CRecordBase & record, const CColumnSet & whereColumns, const CColumnSet & updateColumns, const class CSQLOutputParams *pOptionalOutputParams = NULL );
bool BYieldingUpdateRecords( const CRecordBase & whereRecord, const CColumnSet & whereColumns, const CRecordBase & updateRecord, const CColumnSet & updateColumns, const class CSQLOutputParams *pOptionalOutputParams = NULL );
bool BYieldingDeleteRecords( const CRecordBase & record, const CColumnSet & whereColumns );
//called to handle an update or insert request (an upsert), where if the record exists, it will update the masked fields, otherwise it will insert the full record. All fields should
//be initialized within this record in case of an insert. Note that all of the values will be loaded into a structure named 'Src', so you can reference in the where/update as Src.Column1, etc
bool BYieldingUpdateOrInsert( const CRecordBase& record, const CColumnSet & whereColumns, const CColumnSet & updateColumns, const char* pszWhereClause = NULL, const char* pszUpdateClause = NULL );
void AddRecordParameters( const CRecordBase &record, const CColumnSet & columnSet );
void AddBindParam( const char *pchValue );
void AddBindParam( const int16 nValue );
void AddBindParam( const uint16 uValue );
void AddBindParam( const int32 nValue );
void AddBindParam( const uint32 uValue );
void AddBindParam( const uint64 ulValue );
void AddBindParam( const uint8 *ubValue, const int cubValue );
void AddBindParam( const float fValue );
void AddBindParam( const double dValue );
void AddBindParamRaw( EGCSQLType eType, const byte *pubData, uint32 cubData );
void ClearParams();
IGCSQLResultSetList *GetResults();
uint32 GetResultSetCount();
uint32 GetResultSetRowCount( uint32 unResultSet );
CSQLRecord GetResultRecord( uint32 unResultSet, uint32 unRow );
private:
enum EReadSingleResultResult
{
eReadSingle_Error, // something went wrong in the DB or the data was in a format we didn't expect
eReadSingle_ResultFound, // we found a single result and copied the value -- all is well!
eReadSingle_UseDefault, // we didn't find any results but we specified a value in advance for this case
};
EReadSingleResultResult BYieldingExecuteSingleResultDataInternal( const char *pchName, const char *pchSQLCommand, EGCSQLType eType, uint8 **pubData, uint32 *punSize, uint32 *pcRowsAffected, bool bHasDefaultValue );
private:
void RunListeners_Commit();
void RunListeners_Rollback();
CGCSQLQuery *CurrentQuery();
ESchemaCatalog m_eSchemaCatalog;
CGCSQLQuery *m_pCurrentQuery;
CGCSQLQueryGroup *m_pQueryGroup;
bool m_bInTransaction;
std::vector< std::function<void (void)> > m_vecCommitListeners;
std::vector< std::function<void (void)> > m_vecRollbackListeners;
};
//this class will build up and cache an update or insert query. These can be pretty long and expensive to generate, so the intent of this object is to make a static version which can then
//be submitted to various SQL operations
class CSQLUpdateOrInsert
{
public:
//builds up a query that will insert or merge. The optional clauses can be provided, and if need to reference existing tables, S=Source (new one being added) D=Destination (table being updated)
//NOTE: You cannot use ?'s in the clauses since the parameters are reset and filled in with the provided object
//NOTE: update columns can be empty along with a NULL update clause to simply do an 'insert if it doesn't exist' command
CSQLUpdateOrInsert( const char* pszName, int nTable, const CColumnSet & whereColumns, const CColumnSet & updateColumns, const char* pszWhereClause = NULL, const char* pszUpdateClause = NULL );
//called to execute the query on the provided record. Note that this will clear any previously set parameters for the query so it can fill in the provided record
bool BYieldingExecute( CSQLAccess& sqlAccess, const CRecordBase& record, uint32 *out_punRowsAffected = NULL ) const;
private:
//the display name for our query
CUtlString m_sName;
//the formatted query to run
CUtlString m_sQuery;
//which table we were compiled against to ensure we aren't used on the wrong objects
int m_nTable;
};
#define FOR_EACH_SQL_RESULT( sqlAccess, resultSet, record ) \
for( CSQLRecord record = (sqlAccess).GetResultRecord( resultSet, 0 ); record.IsValid(); record.NextRow() )
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CSQLOutputParams
{
public:
CSQLOutputParams( const CRecordBase *pRecord, const CColumnSet& ColumnSet )
: m_pRecord( pRecord )
, m_ColumnSet( ColumnSet )
{
Assert( pRecord );
Assert( pRecord->GetPRecordInfo() == ColumnSet.GetRecordInfo() );
}
const CRecordBase& GetRecord() const { return *m_pRecord; }
const CColumnSet& GetColumnSet() const { return m_ColumnSet; }
private:
const CRecordBase *m_pRecord;
const CColumnSet& m_ColumnSet;
};
//-----------------------------------------------------------------------------
// Purpose: templatized version of querying for a single value
//-----------------------------------------------------------------------------
template <typename TReturn, typename TCast>
bool CSQLAccess::BYieldingExecuteSingleResult( const char *pchName, const char *pchSQLCommand, EGCSQLType eType, TReturn *pResult, uint32 *pcRowsAffected )
{
uint8 *pubData;
uint32 cubData;
if( CSQLAccess::BYieldingExecuteSingleResultDataInternal( pchName, pchSQLCommand, eType, &pubData, &cubData, pcRowsAffected, false ) != eReadSingle_ResultFound )
return false;
*pResult = *( (TCast *)pubData );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: templatized version of querying for a single value
//-----------------------------------------------------------------------------
template <typename TReturn, typename TCast>
bool CSQLAccess::BYieldingExecuteSingleResultWithDefault( const char *pchName, const char *pchSQLCommand, EGCSQLType eType, TReturn *pResult, TReturn defaultValue, uint32 *pcRowsAffected )
{
uint8 *pubData;
uint32 cubData;
EReadSingleResultResult eResult = CSQLAccess::BYieldingExecuteSingleResultDataInternal( pchName, pchSQLCommand, eType, &pubData, &cubData, pcRowsAffected, true );
if ( eResult == eReadSingle_Error )
return false;
if ( eResult == eReadSingle_ResultFound )
{
*pResult = *( (TCast *)pubData );
}
else
{
Assert( eResult == eReadSingle_UseDefault );
*pResult = defaultValue;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Reads a list of records from the DB according to the specified where
// clause
// Input: pRecordBase - record to insert
// Output: true if successful, false otherwise
//-----------------------------------------------------------------------------
template< typename SchClass_t>
bool CSQLAccess::BYieldingReadRecordsWithWhereClause( CUtlVector< SchClass_t > *pvecRecords, const char *pchWhereClause, const CColumnSet & readSet, const char *pchTopClause, const char* pchOrderClause )
{
AssertMsg( !BInTransaction(), "BYieldingReadRecordsWithWhereClause is not supported in a transaction" );
if( BInTransaction() )
return false;
Assert( !readSet.IsEmpty() );
TSQLCmdStr sStatement;
BuildSelectStatementText( &sStatement, readSet, pchTopClause );
//finished our select, so add our where filter if provided
if( pchWhereClause )
{
sStatement.Append( " WHERE " );
sStatement.Append( pchWhereClause );
}
//finished our where, so add our order by clause if provided
if( pchOrderClause )
{
sStatement.Append( " ORDER BY " );
sStatement.Append( pchOrderClause );
}
return BYieldingReadRecordsWithQuery< SchClass_t >( pvecRecords, sStatement, readSet );
}
//-----------------------------------------------------------------------------
// Purpose: Inserts a new record into the DB and reads non-insertable fields back
// into the record.
// Input: pRecordBase - record to insert
// Output: true if successful, false otherwise
//-----------------------------------------------------------------------------
template< typename SchClass_t>
bool CSQLAccess::BYieldingReadRecordsWithQuery( CUtlVector< SchClass_t > *pvecRecords, const char *sQuery, const CColumnSet & readSet )
{
AssertMsg( !BInTransaction(), "BYieldingReadRecordsWithQuery is not supported in a transaction" );
if( BInTransaction() )
return false;
Assert(!readSet.IsEmpty() );
if( !BYieldingExecute( NULL, sQuery ) )
return false;
Assert( GetResultSetCount() == 1 );
if ( GetResultSetCount() != 1 )
return false;
// make sure the types are the same
IGCSQLResultSet *pResultSet = m_pQueryGroup->GetResults()->GetResultSet( 0 );
return CopyResultToSchVector( pResultSet, readSet, pvecRecords );
}
} // namespace GCSDK
#include "tier0/memdbgoff.h"
#endif // SQLACCESS_H
+54
View File
@@ -0,0 +1,54 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================
#ifndef GCSQLRECORD_H
#define GCSQLRECORD_H
namespace GCSDK
{
class CSQLRecord
{
public:
CSQLRecord( uint32 unRow, IGCSQLResultSet *pResultSet );
CSQLRecord( );
~CSQLRecord();
void Init( uint32 unRow, IGCSQLResultSet *pResultSet );
bool BWriteToRecord( CRecordBase *pRecord, const CColumnSet & csWriteFields );
bool BGetColumnData( uint32 unColumn, uint8 **ppubField, int *pcubField );
bool BGetColumnData( uint32 unColumn, uint8 **ppubField, size_t *pcubField );
bool BGetStringValue( uint32 unColumn, const char **ppchVal );
bool BGetStringValue( uint32 unColumn, CFmtStr1024 *psVal );
bool BGetIntValue( uint32 unColumn, int *pnVal );
bool BGetInt16Value( uint32 unColumn, int16 *pnVal );
bool BGetInt64Value( uint32 unColumn, int64 *puVal );
bool BGetUint64Value( uint32 unColumn, uint64 *puVal );
bool BGetByteValue( uint32 unColumn, byte *pVal );
bool BGetBoolValue( uint32 unColumn, bool *pVal );
bool BGetUint32Value( uint32 unColumn, uint32 *puVal );
bool BGetUint16Value( uint32 unColumn, uint16 *puVal );
bool BGetUint8Value( uint32 unColumn, uint8 *puVal );
bool BGetFloatValue( uint32 unColumn, float *pfVal );
bool BGetDoubleValue( uint32 unColumn, double *pdVal );
void RenderField( uint32 unColumn, int cchBuffer, char *pchBuffer );
bool NextRow();
bool IsValid() const { return m_pResultSet != NULL; }
private:
bool BValidateColumnIndex( uint32 unColumn );
IGCSQLResultSet *m_pResultSet;
uint32 m_unRow;
};
} // namespace GCSDK
#endif // GCSQLRECORD_H
+113
View File
@@ -0,0 +1,113 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================
#ifndef SQLUTIL_H
#define SQLUTIL_H
#ifdef _WIN32
#pragma once
#endif
namespace GCSDK
{
typedef CFmtStrN< 4096 > TSQLCmdStr;
// Returns a long (1024 char) string of "?,?,?,?,?"... for use in IN clauses or INSERT statements
const char *GetInsertArgString();
// Returns the number of characters that should be used for the specified number of parameters
uint32 GetInsertArgStringChars( uint32 nNumParams );
// Returns the maximum number of parameters that are supported by the GetInsertArgString string
uint32 GetInsertArgStringMaxParams();
void ConvertFieldToText( EGCSQLType eFieldType, uint8 *pubRecord, int cubRecord, char *rgchField, int cchField, bool bQuoteString = true );
void ConvertFieldArrayToInText( const CColumnInfo &columnInfo, byte *pubData, int cubData, char *rgchResult, bool bForPreparedStatement );
char *SQLTypeFromField( const CColumnInfo &colInfo, char *pchBuf, int cchBuf );
void EscapeStringValue( char *rgchField, int cchField );
void AppendConstraints( const CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfo, bool bForAdd, CFmtStrMax & sCmd );
void AppendTableConstraints( CRecordInfo *pRecordInfo, CFmtStrMax & sCmd );
void AppendConstraint( const char *pchTableName, const char *pchColumnName, int nColFlagConstraint, bool bForAdd, bool bClustered,
CFmtStrMax & sCmd, int nFillFactor );
void BuildTablePKConstraintText( TSQLCmdStr *psStatement, CRecordInfo *pRecordInfo );
//void BuildSelectStatementText( CUtlVector<CQuery> *pVecQuery, bool bForPreparedStatement, char *pchStatement, int cchStatement );
void BuildInsertStatementText( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo );
void BuildInsertAndReadStatementText( TSQLCmdStr *psStatement, CUtlVector<int> *pvecOutputFields, const CRecordInfo *pRecordInfo ) ;
void BuildMergeStatementTextOnPKWhenMatchedUpdateWhenNotMatchedInsert( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo );
void BuildMergeStatementTextOnPKWhenNotMatchedInsert( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo );
void BuildSelectStatementText( TSQLCmdStr *psStatement, const CColumnSet & selectSet, const char *pchTopClause = NULL );
void BuildUpdateStatementText( TSQLCmdStr *psStatement, const CColumnSet & columnSet );
void BuildDeleteStatementText( TSQLCmdStr *psStatement, const CRecordInfo* pRecordInfo );
void AppendWhereClauseText( TSQLCmdStr *psClause, const CColumnSet & columnSet );
void BuildOutputClauseText( TSQLCmdStr *psClause, const CColumnSet & columnSet );
template< typename T >
bool CopyResultToSchVector( IGCSQLResultSet *pResultSet, const CColumnSet & readSet, CUtlVector< T > *pvecRecords )
{
if ( pResultSet->GetRowCount() == 0 )
return true;
FOR_EACH_COLUMN_IN_SET( readSet, nColumnIndex )
{
EGCSQLType eRecordType = readSet.GetColumnInfo( nColumnIndex ).GetType();
EGCSQLType eResultType = pResultSet->GetColumnType( nColumnIndex );
Assert( eResultType == eRecordType );
if( eRecordType != eResultType )
return false;
}
for( CSQLRecord sqlRecord( 0, pResultSet ); sqlRecord.IsValid(); sqlRecord.NextRow() )
{
int nRecord = pvecRecords->AddToTail();
FOR_EACH_COLUMN_IN_SET( readSet, nColumnIndex )
{
uint8 *pubData;
uint32 cubData;
DbgVerify( sqlRecord.BGetColumnData( nColumnIndex, &pubData, (int*)&cubData ) );
DbgVerify( pvecRecords->Element( nRecord ).BSetField( readSet.GetColumn( nColumnIndex), pubData, cubData ) );
}
}
return true;
}
//EResult UpdateOrInsertUnique( CSQLAccess &sqlAccess, int iTable, int iField, CRecordBase *pRecordBase, int iIndexID );
//bool BIsDuplicateInsertAttempt( const CSQLErrorInfo *pErr );
#define EXIT_WITH_SQL_FAILURE( ret ) { nRet = ret; goto Exit; }
#define EXIT_ON_BOOL_FAILURE( bRet, msg ) \
{ \
if ( false == bRet ) \
{ \
SetSQLError( msg ); \
nRet = SQL_ERROR; \
goto Exit; \
} \
}
#define SAFE_CLOSE_STMT( x ) \
if ( NULL != (x) ) \
{ \
SQLFreeHandle( SQL_HANDLE_STMT, (x) ); \
(x) = NULL; \
}
#define SAFE_FREE_HANDLE( x, y ) \
if ( NULL != (x) ) \
{ \
SQLFreeHandle( y, (x) ); \
(x) = NULL; \
}
} // namespace GCSDK
#endif // SQLUTIL_H
+516
View File
@@ -0,0 +1,516 @@
//========= Copyright © 1996-2010, Valve LLC, All rights reserved. ============
//
// Purpose: Header for CWebAPIResponse objects
//
//=============================================================================
#ifndef WEBAPI_RESPONSE_H
#define WEBAPI_RESPONSE_H
#ifdef _WIN32
#pragma once
#endif
#include "bufferpool.h"
namespace GCSDK
{
enum EWebAPIOutputFormat
{
k_EWebAPIOutputFormat_JSON = 1,
k_EWebAPIOutputFormat_XML = 2,
k_EWebAPIOutputFormat_VDF = 3,
k_EWebAPIOutputFormat_ParameterEncoding = 4,
};
enum EWebAPIValueType
{
// Object is the initial value
k_EWebAPIValueType_Object = 0,
k_EWebAPIValueType_Int32 = 1,
k_EWebAPIValueType_Int64 = 2,
k_EWebAPIValueType_UInt32 = 3,
k_EWebAPIValueType_UInt64 = 4,
k_EWebAPIValueType_Double = 5,
k_EWebAPIValueType_String = 6,
k_EWebAPIValueType_BinaryBlob = 7,
k_EWebAPIValueType_Bool = 8,
k_EWebAPIValueType_Null = 9,
k_EWebAPIValueType_NumericArray = 10,
};
class CWebAPIValues;
class CWebAPIResponse
{
public:
CWebAPIResponse();
~CWebAPIResponse();
// Set the HTTP status code for the response
void SetStatusCode( EHTTPStatusCode eStatusCode ) { m_eStatusCode = eStatusCode; }
// Set how many seconds until this response expires
void SetExpirationSeconds( uint32 unExpirationSeconds ) { m_unExpirationSeconds = unExpirationSeconds; }
// Set when this response was last modified
void SetLastModified( RTime32 rtLastModified ) { m_rtLastModified = rtLastModified; }
// Get the status code for the response
EHTTPStatusCode GetStatusCode() const { return m_eStatusCode; }
// Get how many seconds until this response expires
uint32 GetExpirationSeconds() const { return m_unExpirationSeconds; }
// Get when the response was last modified
RTime32 GetLastModified() const { return m_rtLastModified; }
// extended arrays include their element name as an object in JSON and VDF output formats
void SetExtendedArrays( bool bExtendedArrays ) { m_bExtendedArrays = bExtendedArrays; }
bool HasExtendedArrays() const { return m_bExtendedArrays; }
// Outputs formatted data to buffer
bool BEmitFormattedOutput( EWebAPIOutputFormat eFormat, CUtlBuffer &outputBuffer, size_t unMaxResultSize );
// Resets the response to be empty
void Clear();
// Create the root value element in the response
CWebAPIValues *CreateRootValue( const char *pchName );
const CWebAPIValues *GetRootValue() const { return m_pValues; }
CWebAPIValues *GetRootValue() { return m_pValues; }
// Anonymous root nodes affect only JSON output and, when enabled, result in
// the omission of the extra set of { } braces and the name of the root node.
//
// So this JSON:
// { "root" : { "key1" : "value1", "key2" : "value2" } }
// becomes:
// { "key1" : "value1", "key2" : "value2" }
void SetJSONAnonymousRootNode( bool bAnonymousRootNode ) { m_bJSONAnonymousRootNode = bAnonymousRootNode; }
bool HasJSONAnonymousRootNode() const { return m_bJSONAnonymousRootNode; }
private:
// Emits JSON formatted representation of response
bool BEmitJSON( CUtlBuffer &outputBuffer, size_t unMaxResultSize );
// Emits KeyValues .vdf style formatted representation of response
bool BEmitVDF( CUtlBuffer &outputBuffer, size_t unMaxResultSize );
// Emits XML formatted representation of response
bool BEmitXML( CUtlBuffer &outputBuffer, size_t unMaxResultSize );
// parameter encoding, as used in a lot of open standards
bool BEmitParameterEncoding( CUtlBuffer &outputBuffer );
CWebAPIValues *m_pValues;
EHTTPStatusCode m_eStatusCode;
uint32 m_unExpirationSeconds;
RTime32 m_rtLastModified;
bool m_bExtendedArrays;
bool m_bJSONAnonymousRootNode;
};
class CWebAPIValues
{
// !FIXME! DOTAMERGE
//DECLARE_CLASS_MEMPOOL_MT( CWebAPIValues );
private:
void InitInternal( CWebAPIValues *pParent, int nNamePos, EWebAPIValueType eValueType, const char *pchArrayElementNames );
CWebAPIValues( CWebAPIValues *pParent, int nNamePos, EWebAPIValueType eValueType, const char *pchArrayElementNames = NULL );
CWebAPIValues( CWebAPIValues *pParent, const char *pchName, EWebAPIValueType eValueType, const char *pchArrayElementNames = NULL );
public:
explicit CWebAPIValues( const char *pchName );
CWebAPIValues( const char *pchName, const char *pchArrayElementNames );
~CWebAPIValues();
#ifdef GC
// Gets the buffer pool used to reduce allocs in CWebAPIValues
static CBufferPoolMT &GetBufferPool();
#endif
//
// Child node handling
//
// Create a child object of this node, all children of the resultant
// object must be named.
CWebAPIValues *CreateChildObject( const char *pchName );
// Return an existing child object - otherwise create one and return that.
CWebAPIValues *FindOrCreateChildObject( const char *pchName );
// Add a child object to the array, this should only be called on objects that are of the array type
CWebAPIValues *AddChildObjectToArray();
// Add a child array to the array, this should only be called on objects that are of the array type
CWebAPIValues *AddChildArrayToArray( const char * pchArrayElementNames );
// Create a child array of this node. Note that array nodes can only
// have un-named children, in XML the pchArrayElementNames value will be used
// as the element name for each of the children of the array, in JSON it will simply
// be a numerically indexed [] array.
CWebAPIValues *CreateChildArray( const char *pchName, const char *pchArrayElementNames );
// Find first matching child by name, O(N) on number of children, this class isn't designed for searching
CWebAPIValues * FindChild( const char *pchName );
const CWebAPIValues * FindChild( const char *pchName ) const { return const_cast<CWebAPIValues *>(this)->FindChild( pchName ); }
// Get the first child of this node
CWebAPIValues * GetFirstChild();
const CWebAPIValues * GetFirstChild() const { return m_pFirstChild; }
// Call this on the returned value from GetFirstChild() or a previous GetNextChild() call to
// proceed to the next child of the parent GetFirstChild() was originally called on.
CWebAPIValues * GetNextChild();
const CWebAPIValues * GetNextChild() const { return m_pNextPeer; }
// Returns the parent of this node or NULL if this is the root of a tree
CWebAPIValues * GetParent();
const CWebAPIValues * GetParent() const { return m_pParent; }
// Deletes the child with the given name - no-op if no child by that name
void DeleteChild( const char *pchName );
//
// Setters
//
// Set string value
void SetStringValue( const char *pchValue );
// Set int32 value
void SetInt32Value( int32 nValue );
// Set uint32 value
void SetUInt32Value( uint32 unValue );
// Set int64 value
void SetInt64Value ( int64 lValue );
// Set uint64 value
void SetUInt64Value( uint64 ulValue );
// Set double value
void SetDoubleValue( double flValue );
// Set binary blob value
void SetBinaryValue( const uint8 *pValue, uint32 unBytes );
// Set boolean value
void SetBoolValue( bool bValue );
// Set boolean value
void SetNullValue( );
//
// Accessors
//
// Get the name of the current node
const char *GetName() const { return m_nNamePos >= 0 ? ( (const char *)m_pStringBuffer->Base() + m_nNamePos ) : NULL; }
// get the name of the elements of this numeric array (if this is an array)
const char *GetElementName() const { return m_nArrayChildElementNamePos >= 0 ? ( (const char *)m_pStringBuffer->Base() + m_nArrayChildElementNamePos ) : NULL; }
// Get the type currently held by the node
EWebAPIValueType GetType() const;
// returns true if this is an object
bool IsObject() const { return GetType() == k_EWebAPIValueType_Object; }
// returns true if this is an object
bool IsArray() const { return GetType() == k_EWebAPIValueType_NumericArray; }
// Get int32 value
int32 GetInt32Value() const;
// Get uint32 value
uint32 GetUInt32Value() const;
// Get int64 value
int64 GetInt64Value() const;
// Get uint64 value
uint64 GetUInt64Value() const;
// Get double value
double GetDoubleValue() const;
// Get string value
void GetStringValue( CUtlString &stringOut ) const;
// Get binary blob value
void GetBinaryValue( CUtlBuffer &bufferOut ) const;
// Get bool value
bool GetBoolValue() const;
// Get Null value
bool IsNullValue() const { return GetType() == k_EWebAPIValueType_Null; }
//
// Child Setters
//
// Set string value
void SetChildStringValue( const char *pchChildName, const char *pchValue );
// Set int32 value
void SetChildInt32Value( const char *pchChildName, int32 nValue );
// Set uint32 value
void SetChildUInt32Value( const char *pchChildName, uint32 unValue );
// Set int64 value
void SetChildInt64Value ( const char *pchChildName, int64 lValue );
// Set uint64 value
void SetChildUInt64Value( const char *pchChildName, uint64 ulValue );
// Set double value
void SetChildDoubleValue( const char *pchChildName, double flValue );
// Set binary blob value
void SetChildBinaryValue( const char *pchChildName, const uint8 *pValue, uint32 unBytes );
// Set boolean value
void SetChildBoolValue( const char *pchChildName, bool bValue );
// Set null value
void SetChildNullValue( const char *pchChildName );
//
// Accessors
//
// Get int32 value
int32 GetChildInt32Value( const char *pchChildName, int32 nDefault = 0 ) const;
// Get uint32 value
uint32 GetChildUInt32Value( const char *pchChildName, uint32 unDefault = 0 ) const;
// Get int64 value
int64 GetChildInt64Value( const char *pchChildName, int64 lDefault = 0 ) const;
// Get uint64 value
uint64 GetChildUInt64Value( const char *pchChildName, uint64 ulDefault = 0 ) const;
// Get double value
double GetChildDoubleValue( const char *pchChildName, double flDefault = 0 ) const;
// Get string value
void GetChildStringValue( CUtlString &stringOut, const char *pchChildName, const char *pchDefault ) const;
// Get binary blob value (returns false if the child wasn't found)
bool BGetChildBinaryValue( CUtlBuffer &bufferOut, const char *pchChildName ) const;
// Get bool value
bool GetChildBoolValue( const char *pchChildName, bool bDefault = false ) const;
// get null value
bool IsChildNullValue( const char *pchChildName ) const;
//
// Output methods
//
// Emits JSON formatted representation of response
static bool BEmitJSONRecursive( const CWebAPIValues *pCurrent, CUtlBuffer &outputBuffer, int nTabLevel, size_t unMaxResultSize, bool bIncludeArrayElementName = true );
// Emits KeyValues .vdf style formatted representation of response
static bool BEmitVDFRecursive( const CWebAPIValues *pCurrent, CUtlBuffer &outputBuffer, int nTabLevel, uint32 nArrayElement, size_t unMaxResultSize, bool bIncludeArrayElementName = true );
// Emits XML formatted representation of response
static bool BEmitXMLRecursive( const CWebAPIValues *pCurrent, CUtlBuffer &outputBuffer,int nTabLevel, size_t unMaxResultSize );
//
// Parsing methods
//
// parses JSON into a tree of CWebAPIValues nodes.
static CWebAPIValues * ParseJSON( CUtlBuffer &inputBuffer );
static CWebAPIValues * ParseJSON( const char *pchJSONString );
//
// Utility methods
//
// copies the children and type from the specified node into this node
void CopyFrom( const CWebAPIValues *pSource );
private:
// sets the name of the node when constructing or copying
void SetName( const char * pchName );
// Clears any existing value, freeing memory if needed
void ClearValue();
// Assert that we don't have any child nodes, this is used when setting a native type value. We don't
// support having both our own value and children. You are either an array of more values, or you are a value yourself.
void AssertNoChildren();
// Internal helper for creating children
CWebAPIValues *CreateChildInternal( const char *pchName, EWebAPIValueType eValueType, const char *pchArrayElementNames = NULL );
// Name of this node
int32 m_nNamePos;
// Data value contained in this node
EWebAPIValueType m_eValueType;
struct WebAPIBinaryValue_t
{
int32 m_nDataPos;
uint32 m_unBytes;
};
union
{
int32 m_nValue;
int64 m_lValue;
uint32 m_unValue;
uint64 m_ulValue;
double m_flValue;
int32 m_nStrValuePos;
bool m_bValue;
int32 m_nArrayChildElementNamePos;
WebAPIBinaryValue_t m_BinaryValue;
};
CWebAPIValues * m_pFirstChild;
CWebAPIValues * m_pLastChild;
CWebAPIValues * m_pNextPeer;
CWebAPIValues * m_pParent;
CUtlBuffer *m_pStringBuffer;
};
#define FOR_EACH_WEBAPI_CHILD( pParentParam, pChildParam ) \
for( CWebAPIValues *pChildParam = pParentParam->GetFirstChild(); pChildParam != NULL; pChildParam = pChildParam->GetNextChild() )
}
#include "tier0/memdbgon.h"
namespace GCSDK
{
//-----------------------------------------------------------------------------
// Purpose: KeyValues wrapper that automatically deletes itself on close
//-----------------------------------------------------------------------------
class CWebAPIValuesAD
{
public:
CWebAPIValuesAD()
{
m_pwav = NULL;
}
// create a webapivalues object of the object type
CWebAPIValuesAD( const char *pchName )
{
m_pwav = new CWebAPIValues( pchName );
}
// create a webapivalues object of the array type
CWebAPIValuesAD( const char *pchName, const char *pchArrayElementName )
{
m_pwav = new CWebAPIValues( pchName, pchArrayElementName );
}
CWebAPIValuesAD( const CWebAPIValuesAD &rhs )
{
m_pwav = NULL;
Copy( rhs.m_pwav );
}
CWebAPIValuesAD( const CWebAPIValues *pwav )
{
m_pwav = NULL;
Copy( pwav );
}
~CWebAPIValuesAD()
{
delete m_pwav;
}
CWebAPIValues *operator->() { if ( !m_pwav ) m_pwav = new CWebAPIValues( "root" ); return m_pwav; }
operator CWebAPIValues *() { if ( !m_pwav ) m_pwav = new CWebAPIValues( "root" ); return m_pwav; }
operator const CWebAPIValues *() const { return m_pwav; }
CWebAPIValuesAD & operator= ( const CWebAPIValuesAD &rhs )
{
Copy( rhs.m_pwav );
return *this;
}
void Take( CWebAPIValues *pwav )
{
if ( pwav )
{
delete m_pwav;
m_pwav = pwav;
}
else if ( m_pwav )
{
delete m_pwav;
m_pwav = NULL;
}
}
void Copy( const CWebAPIValues *pwav )
{
if ( m_pwav )
delete m_pwav;
if ( pwav )
{
if( pwav->IsArray() )
m_pwav = new CWebAPIValues( pwav->GetName(), pwav->GetElementName() );
else
m_pwav = new CWebAPIValues( pwav->GetName() );
m_pwav->CopyFrom( pwav );
}
else
m_pwav = NULL;
}
private:
CWebAPIValues *operator=(CWebAPIValues *); // use Take() or Copy()
CWebAPIValues *m_pwav;
};
// use to decode binary values
bool Base64Decode( const char *pchData, uint32 cchDataMax, uint8 *pubDecodedData, uint32 *pcubDecodedData, bool bIgnoreInvalidCharacters );
bool Base64Encode( const uint8 *pubData, uint32 cubData, char *pchEncodedData, uint32 *pcchEncodedData, const char *pszLineBreak = NULL );
uint32 Base64EncodeMaxOutput( const uint32 cubData, const char *pszLineBreak = NULL );
inline bool Base64EncodeIntoUTLMemory( const uint8 *pubData, uint32 cubData, CUtlMemory<char>& out_pBuffer )
{
// Stomp buffer and pre-allocate space.
out_pBuffer.EnsureCapacity( Base64EncodeMaxOutput( cubData ) );
// Perform encode, swallowing the used space output.
uint32 unBufferCapacity = out_pBuffer.Count();
return Base64Encode( pubData, cubData, out_pBuffer.Base(), &unBufferCapacity );
}
}
namespace ProtoBufHelper
{
bool RecursiveAddProtoBufToWebAPIValues( GCSDK::CWebAPIValues *pWebAPIRoot, const ::google::protobuf::Message & msg );
bool ParseWebAPIValues( ::google::protobuf::Message & msg, const GCSDK::CWebAPIValues *pWebAPIRoot, bool bEnforceRequired = false );
}
#include "tier0/memdbgoff.h"
#endif // WEBAPI_RESPONSE_H
+386
View File
@@ -0,0 +1,386 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: A thread pool implementation. You give it CWorkItems,
// it processes them asynchronously, and hands them back to you when they've
// been completed.
//
// To declare a queue, provide the implementation of a CWorkItem subtype,
// the thread name prefix for threads in the pool, and the number of work
// threads you want.
//
// CNet uses this class to offload encryption to a separate thread,
// so that's a good place to start looking for usage examples.
//
//=============================================================================
#ifndef WORKTHREADPOOL_H
#define WORKTHREADPOOL_H
#ifdef _WIN32
#pragma once
#endif
#include <refcount.h>
#include <reliabletimer.h>
#include "jobtime.h"
// forward declaration for CTSQueue which we can't statically allocate as our member
// because of alignment issues on Win64
template <class T, bool bTestOptimizer>
class CTSQueue;
namespace GCSDK {
// forward declarations
class CWorkThread;
class CJobMgr;
// these functions return pointers to fixed string in the code section. We need this for VPROF nodes
#define DECLARE_WORK_ITEM( classname ) \
virtual const char* GetDispatchCompletedName() const { return #classname"::DispatchCompleted"; } \
virtual const char* GetThreadProcessName() const { return #classname"::ThreadProcess"; }
//-----------------------------------------------------------------------------
// Purpose: Work item base class. Derive from this for specific work item types.
// The derived type ideally should be self-contained with all data it
// needs to perform the work.
//-----------------------------------------------------------------------------
class CWorkItem : public CRefCount
{
public:
CWorkItem()
: m_JobID( k_GIDNil ),
m_bRunning( false ),
m_bResubmit( false ),
m_bCanceled( false ),
m_ulSequenceNumber( 0 )
{
m_jobtimeTimeout.SetLTime( 0 );
m_jobtimeQueued.SetToJobTime();
}
CWorkItem( JobID_t jobID )
: m_JobID( jobID ),
m_bRunning( false ),
m_bResubmit( false ),
m_bCanceled( false ),
m_ulSequenceNumber( 0 )
{
m_jobtimeTimeout.SetLTime( 0 );
m_jobtimeQueued.SetToJobTime();
}
CWorkItem( JobID_t jobID, int64 cTimeoutMicroseconds )
: m_JobID( jobID ),
m_bRunning( false ),
m_bResubmit( false ),
m_bCanceled( false ),
m_ulSequenceNumber( 0 )
{
SetPreExecuteTimeout( cTimeoutMicroseconds );
m_jobtimeQueued.SetToJobTime();
}
void SetJobID( JobID_t jobID )
{
Assert(jobID != k_GIDNil) ;
m_JobID = jobID;
}
JobID_t GetJobID() const { return m_JobID; }
bool HasTimedOut() const { return m_jobtimeTimeout.LTime() != 0 && m_jobtimeTimeout.CServerMicroSecsPassed() > 0; }
int64 WaitingTime() const { return m_jobtimeQueued.CServerMicroSecsPassed(); }
void SetPreExecuteTimeout( int64 cMicroSeconds ) { m_jobtimeTimeout.SetFromJobTime( cMicroSeconds ); }
bool BPreExecuteTimeoutSet( ) const { return m_jobtimeTimeout.LTime() != 0; }
void ForceTimeOut() { m_jobtimeTimeout.SetFromJobTime( -1 );}
bool BIsRunning() const { return m_bRunning; } // true if running right now
bool WasCancelled() const { return m_bCanceled; }
void SetCycleCount( CCycleCount& cycleCount ) { m_CycleCount = cycleCount ; }
CCycleCount GetCycleCount() { return m_CycleCount; }
uint64 GetSequenceNumber() { return m_ulSequenceNumber; }
// Work threads can call this to force a work item to be reprocessed (added to the end of the process queue)
void SetResubmit( bool bResubmit ) { m_bResubmit = bResubmit; }
// these functions return pointers to fixed string in the code section.
// We need this for VPROF nodes, you must use the DECLARE_WORK_ITEM macro
virtual const char* GetDispatchCompletedName() const = 0;
virtual const char* GetThreadProcessName() const = 0;
// Return false if your operation failed in some way that you would want to know about
// The CWorkThreadPool will count the failures.
virtual bool ThreadProcess( CWorkThread *pThread ) = 0; // called by the worker thread
virtual bool DispatchCompletedWorkItem( CJobMgr *jobMgr ); // called by main loop after item completed
#ifdef DBGFLAG_VALIDATE
virtual void Validate( CValidator &validator, const char *pchName ) {} // Validate our internal structures
#endif
protected:
// note: destructor is private. This is a ref-counted object, private destructor ensures callers can't accidentally delete
// directly, or declare on stack
virtual ~CWorkItem() { }
friend class CWorkThread;
friend class CWorkThreadPool;
uint64 m_ulSequenceNumber; // Sequence number for the work item, used when enforcing output ordering as matching input order
CCycleCount m_CycleCount; // A record of how long it took to execute this particular work item !
private:
bool m_bResubmit; // true if the item should be resubmitted after last run
volatile bool m_bRunning; // true if the work item is running right now
bool m_bCanceled; // true if the work was canceled due to timeout
CJobTime m_jobtimeTimeout; // time at which this result is no longer valid, so it shouldn't start to be processed
CJobTime m_jobtimeQueued;
JobID_t m_JobID;
};
// forward decl
class CWorkThreadPool;
//-----------------------------------------------------------------------------
// Purpose: Generic work thread implementation, to be specialized if necessary
//-----------------------------------------------------------------------------
class CWorkThread : public CThread
{
public:
CWorkThread( CWorkThreadPool *pThreadPool );
CWorkThread( CWorkThreadPool *pThreadPool, const char *pszName );
virtual ~CWorkThread()
{
}
virtual int Run();
virtual void Cancel()
{
}
protected:
CWorkThreadPool *m_pThreadPool; // parent pool
volatile bool m_bExitThread; // set by CWorkThreadPool::StopWorkerThreads and possibly by subclasses of CWorkThread
volatile bool m_bFinished; // set by CWorkThread::Run [note: must still check IsThreadRunning, and/or call Join]
virtual void OnStart() { }
virtual void OnExit() { }
#ifdef DBGFLAG_VALIDATE
public:
virtual void Validate( CValidator &validator, const char *pchName )
{
VALIDATE_SCOPE();
};
#endif // DBGFLAG_VALIDATE
friend class CWorkThreadPool;
};
//-----------------------------------------------------------------------------
// callback class to create work threads
//-----------------------------------------------------------------------------
class IWorkThreadFactory
{
public:
virtual CWorkThread *CreateWorkerThread( class CWorkThreadPool *pWorkThreadPool ) = 0;
};
//-----------------------------------------------------------------------------
// reusable trivial implementation of IWorkThreadFactory
//-----------------------------------------------------------------------------
template<class T>
class CWorkThreadFactory : public IWorkThreadFactory
{
public:
virtual CWorkThread *CreateWorkerThread( class CWorkThreadPool *pWorkThreadPool )
{
return new T( pWorkThreadPool );
}
};
//-----------------------------------------------------------------------------
// Purpose: interface class for object that the WorkThreadPool can signal when
// there are completed work items to process
//-----------------------------------------------------------------------------
class IWorkThreadPoolSignal
{
public:
virtual void Signal() = 0;
};
//-----------------------------------------------------------------------------
// Purpose: pool of work threads.
//-----------------------------------------------------------------------------
class CWorkThreadPool
{
friend class CWorkThread;
public:
static void SetWorkItemCompletedSignal( IWorkThreadPoolSignal *pObject )
{
sm_pWorkItemsCompletedSignal = pObject;
}
CWorkThreadPool( const char *pszThreadNamePfx );
// eventually it might be nice to be able to resize these pools via console command
// in that case, we'd want a constructor like this, and a PoolSize accessor/mutator pair
// it makes this class much more complicated, however (growing the pool is easy, shrinking it
// is less easy) so we'll punt for now.
/* CWorkThreadPool( const char *pszName = "unnamed thread" ) : CWorkThreadPool( pszName, -1 ); */
virtual ~CWorkThreadPool();
// Setting this will ensure that items of the same priority complete and get dispatched in the same order
// they are added to the threadpool. This has a small additional locking overhead and can increase latency
// as items that are actually completed out-of-order have to queue waiting on earlier items.
void SetEnsureOutputOrdering( bool bEnsureOutputOrdering ) { m_bEnsureOutputOrdering = bEnsureOutputOrdering; }
void AllowTimeouts( bool bMayHaveJobTimeouts ) { m_bMayHaveJobTimeouts = bMayHaveJobTimeouts; }
int AddWorkThread( CWorkThread *pThread );
void StartWorkThreads(); // gentlemen, start your engines
void StopWorkThreads(); // stop work threads
bool HasWorkItemsToProcess() const;
// sets it to use dynamic worker thread construction
// if pWorkThreadControl is NULL, just creates a standard CWorkThread object
void SetWorkThreadAutoConstruct( int cMaxThreads, IWorkThreadFactory *pWorkThreadConstructor );
bool AddWorkItem( CWorkItem *pWorkItem ); // add a work item to the queue to process
CWorkItem *GetNextCompletedWorkItem( ); // get next completed work item and it's priority if needed
const char *GetThreadNamePrefix() const { return m_szThreadNamePfx; }
void SetNeverSetEventOnAdd( bool bNeverSet );
bool BNeverSetEventOnAdd() { return m_bNeverSetOnAdd; }
// get count of completed work items
// can't be inline because of m_TSQueueCompleted type
int GetCompletedWorkItemCount() const;
// get count of work items to process
// can't be inline because of m_TSQueueToProcess type
int GetWorkItemToProcessCount() const;
uint64 GetLastUsedSequenceNumber( ) const
{
return m_ulLastUsedSequenceNumber;
}
uint64 GetLastCompletedSequenceNumber( ) const
{
return m_ulLastCompletedSequenceNumber;
}
uint64 GetLastDispatchedSequenceNumber( ) const
{
return m_ulLastDispatchedSequenceNumber;
}
#if 0
uint64 GetAveExecutionTime() const
{
return m_StatExecutionTime.GetUlAvg();
}
uint64 GetAveWaitTime() const
{
return m_StatWaitTime.GetUlAvg();
}
uint64 GetCurrentBacklogTime() const;
#endif
int CountCompletedSuccess() const { return m_cSuccesses; }
int CountRetries() const { return m_cRetries; }
int CountCompletedFailed() const { return m_cFailures; }
bool BDispatchCompletedWorkItems( const CLimitTimer &limitTimer, CJobMgr *pJobMgr );
bool BExiting() const { return m_bExiting; }
int GetWorkerCount() const { return m_WorkThreads.Count(); }
uint GetActiveThreadCount() const { return m_cActiveThreads; }
// make sure you lock before using this
const CWorkThread *GetWorkThread( int iIndex ) const
{
Assert( iIndex >= 0 && iIndex < m_WorkThreads.Count() );
return m_WorkThreads[iIndex];
}
protected:
// STATICS
static IWorkThreadPoolSignal *sm_pWorkItemsCompletedSignal;
// MEMBERS
CWorkItem *GetNextWorkItemToProcess( );
void StartWorkThread( CWorkThread *pWorkThread, int iName );
// meaningful thread name prefix
char m_szThreadNamePfx[32];
// have we actually initialized the threadpool?
bool m_bThreadsInitialized;
// Incoming queue: queue of all work items to process
// must be dynamically allocated for alignment requirements on Win64
CTSQueue< CWorkItem *, false > *m_pTSQueueToProcess;
// Outgoing queues: queue of all completed work items
// must be dynamically allocated for alignment requirements on Win64
CTSQueue< CWorkItem *, false > *m_pTSQueueCompleted;
// Vectors of completed, but out of order and waiting work items, only used when bEnsureOutputOrdering == true
CThreadMutex m_MutexOnItemCompletedOrdered;
CUtlVector< CWorkItem * > m_vecCompletedAndWaiting;
// Should we emit work items in the same order they are received (on a per priority basis)
bool m_bEnsureOutputOrdering;
// Sequence numbers
uint64 m_ulLastUsedSequenceNumber;
uint64 m_ulLastCompletedSequenceNumber;
uint64 m_ulLastDispatchedSequenceNumber;
bool m_bMayHaveJobTimeouts;
CUtlVector< CWorkThread * > m_WorkThreads;
CThreadMutex m_WorkThreadMutex;
CInterlockedUInt m_cThreadsRunning; // how many threads are running
volatile bool m_bExiting; // are we exiting
CThreadEvent m_EventNewWorkItem; // event set when a new work item is available to process
CInterlockedInt m_cActiveThreads;
volatile bool m_bNeverSetOnAdd;
bool m_bAutoCreateThreads;
int m_cMaxThreads;
IWorkThreadFactory *m_pWorkThreadConstructor;
// override this method if you want to do any special handling of completed work items. Default implementation puts
// work items in our completed item queue.
virtual void OnWorkItemCompleted( CWorkItem *pWorkItem );
bool BTryDeleteExitedWorkerThreads();
int m_cSuccesses;
int m_cFailures;
int m_cRetries;
#if 0
CStat m_StatExecutionTime;
CStat m_StatWaitTime;
#endif
CLimitTimer m_LimitTimerCreateNewThreads;
#ifdef DBGFLAG_VALIDATE
public:
void Validate( CValidator &validator, const char *pchName );
#endif
};
} // namespace GCSDK
#endif // WORKTHREAD_H