summaryrefslogtreecommitdiff
path: root/public/gcsdk
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /public/gcsdk
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'public/gcsdk')
-rw-r--r--public/gcsdk/accountdetails.h165
-rw-r--r--public/gcsdk/bufferpool.h67
-rw-r--r--public/gcsdk/directory.h166
-rw-r--r--public/gcsdk/enumutils.h43
-rw-r--r--public/gcsdk/framefunction.h141
-rw-r--r--public/gcsdk/gc_convar.h130
-rw-r--r--public/gcsdk/gc_sharedobjectcache.h409
-rw-r--r--public/gcsdk/gcbase.h834
-rw-r--r--public/gcsdk/gcclient.h99
-rw-r--r--public/gcsdk/gcclient_sharedobjectcache.h246
-rw-r--r--public/gcsdk/gcclientjob.h139
-rw-r--r--public/gcsdk/gcclientsdk.h66
-rw-r--r--public/gcsdk/gcconstants.h202
-rw-r--r--public/gcsdk/gcdirtyfield.h82
-rw-r--r--public/gcsdk/gchost.h24
-rw-r--r--public/gcsdk/gcinterface.h181
-rw-r--r--public/gcsdk/gcjob.h241
-rw-r--r--public/gcsdk/gcleaderboardapi.h40
-rw-r--r--public/gcsdk/gclogger.h131
-rw-r--r--public/gcsdk/gcmsg.h234
-rw-r--r--public/gcsdk/gcparalleljobfarm.h42
-rw-r--r--public/gcsdk/gcreportprinter.h129
-rw-r--r--public/gcsdk/gcschema.h50
-rw-r--r--public/gcsdk/gcsdk.h105
-rw-r--r--public/gcsdk/gcsdk_auto.h8
-rw-r--r--public/gcsdk/gcsession.h236
-rw-r--r--public/gcsdk/gcsqlquery.h163
-rw-r--r--public/gcsdk/gcsqlwritequeue.h131
-rw-r--r--public/gcsdk/gcsteamdefines.h48
-rw-r--r--public/gcsdk/gcsystemaccess.h255
-rw-r--r--public/gcsdk/gcsystemmsgs.h216
-rw-r--r--public/gcsdk/gcwebapi.h103
-rw-r--r--public/gcsdk/gcwebapikey.h45
-rw-r--r--public/gcsdk/gcwgjobmgr.h146
-rw-r--r--public/gcsdk/http.h228
-rw-r--r--public/gcsdk/job.h465
-rw-r--r--public/gcsdk/jobmgr.h430
-rw-r--r--public/gcsdk/jobtime.h60
-rw-r--r--public/gcsdk/messagelist.h174
-rw-r--r--public/gcsdk/msgbase.h870
-rw-r--r--public/gcsdk/msgprotobuf.h473
-rw-r--r--public/gcsdk/netpacket.h48
-rw-r--r--public/gcsdk/netpacketpool.h42
-rw-r--r--public/gcsdk/protobufsharedobject.h165
-rw-r--r--public/gcsdk/refcount.h107
-rw-r--r--public/gcsdk/scheduledfunction.h205
-rw-r--r--public/gcsdk/schemasharedobject.h127
-rw-r--r--public/gcsdk/sdocache.h1190
-rw-r--r--public/gcsdk/sharedobject.h305
-rw-r--r--public/gcsdk/sharedobjectcache.h136
-rw-r--r--public/gcsdk/sharedobjecttransaction.h499
-rw-r--r--public/gcsdk/sqlaccess/columnset.h219
-rw-r--r--public/gcsdk/sqlaccess/record.h467
-rw-r--r--public/gcsdk/sqlaccess/recordinfo.h174
-rw-r--r--public/gcsdk/sqlaccess/schema.h601
-rw-r--r--public/gcsdk/sqlaccess/schemafull.h284
-rw-r--r--public/gcsdk/sqlaccess/schemaupdate.h95
-rw-r--r--public/gcsdk/sqlaccess/sqlaccess.h290
-rw-r--r--public/gcsdk/sqlaccess/sqlrecord.h54
-rw-r--r--public/gcsdk/sqlaccess/sqlutil.h113
-rw-r--r--public/gcsdk/webapi_response.h516
-rw-r--r--public/gcsdk/workthreadpool.h386
62 files changed, 14040 insertions, 0 deletions
diff --git a/public/gcsdk/accountdetails.h b/public/gcsdk/accountdetails.h
new file mode 100644
index 0000000..636eddb
--- /dev/null
+++ b/public/gcsdk/accountdetails.h
@@ -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
diff --git a/public/gcsdk/bufferpool.h b/public/gcsdk/bufferpool.h
new file mode 100644
index 0000000..defcc5e
--- /dev/null
+++ b/public/gcsdk/bufferpool.h
@@ -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
diff --git a/public/gcsdk/directory.h b/public/gcsdk/directory.h
new file mode 100644
index 0000000..dfdef85
--- /dev/null
+++ b/public/gcsdk/directory.h
@@ -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
diff --git a/public/gcsdk/enumutils.h b/public/gcsdk/enumutils.h
new file mode 100644
index 0000000..d211d37
--- /dev/null
+++ b/public/gcsdk/enumutils.h
@@ -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 \ No newline at end of file
diff --git a/public/gcsdk/framefunction.h b/public/gcsdk/framefunction.h
new file mode 100644
index 0000000..dc15bbd
--- /dev/null
+++ b/public/gcsdk/framefunction.h
@@ -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
diff --git a/public/gcsdk/gc_convar.h b/public/gcsdk/gc_convar.h
new file mode 100644
index 0000000..ec6f116
--- /dev/null
+++ b/public/gcsdk/gc_convar.h
@@ -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
diff --git a/public/gcsdk/gc_sharedobjectcache.h b/public/gcsdk/gc_sharedobjectcache.h
new file mode 100644
index 0000000..e7cdfc4
--- /dev/null
+++ b/public/gcsdk/gc_sharedobjectcache.h
@@ -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
diff --git a/public/gcsdk/gcbase.h b/public/gcsdk/gcbase.h
new file mode 100644
index 0000000..fbefbc0
--- /dev/null
+++ b/public/gcsdk/gcbase.h
@@ -0,0 +1,834 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#ifndef GCBASE_H
+#define GCBASE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "gamecoordinator/igamecoordinator.h"
+#include "gamecoordinator/igamecoordinatorhost.h"
+#include "tier1/utlallocation.h"
+#include "gcmsg.h"
+#include "jobmgr.h"
+#include "tier1/thash.h"
+#include "tier1/UtlSortVector.h"
+#include "http.h"
+#include "language.h"
+#include "accountdetails.h"
+#include "gcsession.h"
+
+class CGCMsgGetSystemStatsResponse;
+
+extern EUniverse GetUniverse();
+
+const int16 k_nLockTypeLobby = 3;
+const int16 k_nLockTypeParty = 2;
+const int16 k_nLockTypeIndividual = 1;
+const int16 k_nLockTypeGameServer = 0;
+const int16 k_nLockTypeGroupIDGeneration = -1;
+ // For one-off locks of specific resources. The specific game controls the subtype ordering.
+const int16 k_nLockTypeGameMisc = -10;
+
+namespace GCSDK
+{
+class CGCSession;
+class CGCUserSession;
+class CGCGSSession;
+class CGCSharedObjectCache;
+class CSharedObject;
+class CAccountDetails;
+class CScopedSteamIDLock;
+
+struct PackageLicense_t
+{
+ uint32 m_unPackageID;
+ RTime32 m_rtimeCreated;
+};
+
+#define SERVER_KEY_HASH 0x5a4f4944u
+
+class CGCBase : public IGameCoordinator
+{
+public:
+ CGCBase( );
+ virtual ~CGCBase();
+
+ // Hooks to extend the base behaviors of the IGameCoordinator interface
+ virtual bool OnInit() { return true; }
+ virtual bool OnMainLoopOncePerFrame( CLimitTimer &limitTimer ) { return false; }
+ virtual bool OnMainLoopUntilFrameCompletion( CLimitTimer &limitTimer ) { return false; }
+ virtual void OnUninit() {}
+ // returns true if this function handled the message
+ virtual bool OnMessageFromClient( const CSteamID & senderID, uint32 unMsgType, void *pubData, uint32 cubData ) { return false; }
+ virtual void OnValidate( CValidator &validator, const char *pchName ) {}
+ virtual const char *LocalizeToken( const char *pchToken, ELanguage eLanguage, bool bReturnTokenIfNotFound = true ) { return bReturnTokenIfNotFound ? pchToken : NULL; }
+
+ virtual void YldOnAccountPhoneVerificationChange( CSteamID steamID ) {}
+ virtual void YldOnAccountTwoFactorChange( CSteamID steamID ) {}
+
+ /// The main loop calls Run() on game servers and user sessions until a certain amount of time
+ /// is expired. Thus the user list is iterated, but the iteration is amortized over multiple frames.
+ /// this function is called when a sweep of the user sessions ends and a new one is about to begin.
+ virtual void FinishedMainLoopUserSweep();
+
+ // Life cycle management functions
+
+ // Called to do any yielding initialization work before reporting as fully operational
+ virtual bool BYieldingFinishStartup() = 0;
+
+ // Called to do any yielding work immediately after becoming full operational
+ virtual bool BYieldingPostStartup() = 0;
+
+ // Call to report that we're fully operational
+ void SetStartupComplete( bool bSuccess );
+
+ // Called to do any yielding work before reporting as ready to shutdown
+ virtual void YieldingGracefulShutdown() = 0;
+
+
+ virtual CGCUserSession *CreateUserSession( const CSteamID & steamID, CGCSharedObjectCache *pSOCache ) const;
+ virtual CGCGSSession *CreateGSSession( const CSteamID & steamID, CGCSharedObjectCache *pSOCache, uint32 unServerAddr, uint16 usServerPort ) const;
+ virtual void YieldingSessionStartPlaying( CGCUserSession *pSession ) {}
+ virtual void YieldingSessionStopPlaying( CGCUserSession *pSession ) {}
+ virtual void YieldingSessionStartServer( CGCGSSession *pSession ) {}
+ virtual void YieldingSessionStopServer( CGCGSSession *pSession ) {}
+ virtual void YieldingSOCacheLoaded( CGCSharedObjectCache *pSOCache );
+ virtual void UpdateGSSessionAddress( CGCGSSession *pSession, uint32 unServerAddr, uint16 usServerPort );
+
+ virtual void YieldingPreTestSetup() {}
+
+ // cache management
+ CGCSharedObjectCache *YieldingGetLockedSOCache( const CSteamID &steamID, const char *pszFilename, int nLineNum );
+ CGCSharedObjectCache *YieldingFindOrLoadSOCache( const CSteamID &steamID );
+ CGCSharedObjectCache *FindSOCache( const CSteamID & steamID ); // non-yielding, but may return NULL if the cache exists but is not loaded
+ bool UnloadUnusedCaches( uint32 unMaxCacheCount, CLimitTimer *pLimitTimer = NULL );
+ void YieldingReloadCache( CGCSharedObjectCache *pSOCache );
+ virtual CGCSharedObjectCache *CreateSOCache( const CSteamID &steamID );
+ void FlushInventoryCache( AccountID_t unAccountID );
+
+ CGCUserSession *YieldingGetLockedUserSession( const CSteamID & steamID, const char *pszFilename, int nLineNum );
+ CGCUserSession *FindUserSession( const CSteamID & steamID ) const;
+ bool BUserSessionPending( const CSteamID & steamID ) const;
+
+ CGCGSSession *YieldingGetLockedGSSession( const CSteamID & steamID, const char *pszFilename, int nLineNum );
+ CGCGSSession *YieldingFindOrCreateGSSession( const CSteamID & steamID, uint32 unServerAddr, uint16 usServerPort, const uint8 *pubVarData, uint32 cubVarData );
+ CGCGSSession *FindGSSession( const CSteamID & steamID ) const;
+
+ /// Lookup the user or GS session, depending on whether the supplied
+ /// Steam ID is an individual or gameserver ID
+ CGCSession *FindUserOrGSSession( const CSteamID & steamID ) const;
+
+ CGCSession *YieldingRequestSession( const CSteamID & steamID );
+ bool BYieldingIsOnline( const CSteamID & steamID );
+ bool BSendWebApiRegistration();
+
+ // Will return true, indicating the request was sent, a response was received, and the response had a valid
+ // status code, or false, indicating that there was a transport-layer problem -- the request timed out, or
+ // a response came back with an invalid status code, etc. All "false" return values are meant to indicate
+ // temporary errors.
+ bool BYieldingSendHTTPRequest( const CHTTPRequest *pRequest, CHTTPResponse *pResponse );
+
+ // Possible return values:
+ // k_EResultOK -- everything went fine and pkvResponse has been populated.
+ // k_EResultTimeout -- there was a temporary/transport-layer problem (see "BYieldingSendHTTPRequest").
+ // These are temporary errors, or programming errors on our side.
+ // k_EResultRemoteCallFailed -- we didn't timeout but we got a bad, unparseable, or otherwise invalid response
+ // back and so we don't have data we can use.
+ // k_EResultFail -- something has gone catastrophically wrong and no forward progress can be
+ // expected to be made. May or may not ever be returned.
+ EResult YieldingSendHTTPRequestKV( const CHTTPRequest *pRequest, KeyValues *pkvResponse );
+
+ int GetSOCacheCount() const;
+ bool IsSOCached( const CSharedObject *pObj, uint32 nTypeID ) const;
+
+ int GetUserSessionCount() const;
+ int GetGSSessionCount() const;
+
+ void SetIsShuttingDown();
+ bool GetIsShuttingDown() const { return m_bIsShuttingDown; }
+
+ // Iterate over sessions
+ // WARNING: Don't yield between GetFirst*/GetNext*. Use caution when using
+ // these any time you might have tens of thousands of sessions to iterate through.
+ CGCUserSession **GetFirstUserSession() { return m_hashUserSessions.PvRecordFirst(); }
+ CGCUserSession **GetNextUserSession( CGCUserSession **pUserSession ) { return m_hashUserSessions.PvRecordNext( pUserSession ); }
+ CGCGSSession **GetFirstGSSession() { return m_hashGSSessions.PvRecordFirst(); }
+ CGCGSSession **GetNextGSSession( CGCGSSession **pGSSession ) { return m_hashGSSessions.PvRecordNext( pGSSession ); }
+
+ AppId_t GetAppID() const { return m_unAppID; }
+ bool BIsStartupComplete() const { return m_bStartupComplete; }
+ CJobMgr &GetJobMgr() { return m_JobMgr; }
+
+ CSteamID YieldingGuessSteamIDFromInput( const char *pchInput );
+ bool BYieldingRecordSupportAction( const CSteamID & actorID, const CSteamID & targetID, const char *pchData, const char *pchNote );
+ void PostAlert( EAlertType eAlertType, bool bIsCritical, const char *pchAlertText, const CUtlVector< CUtlString > *pvecExtendedInfo = NULL, bool bAlsoSpew = true );
+ const CAccountDetails *YieldingGetAccountDetails( const CSteamID & steamID, bool bForceReload = false );
+ const char *YieldingGetPersonaName( const CSteamID & steamID, const char *pchUnknownName = NULL );
+ void PreloadPersonaName( const CSteamID & steamID );
+ void ClearCachedPersonaName( const CSteamID & steamID );
+ bool BYieldingGetAccountLicenses( const CSteamID & steamID, CUtlVector< PackageLicense_t > & vecPackages );
+ bool BYieldingLookupAccount( EAccountFindType eFindType, const char *pchInput, CUtlVector< CSteamID > *prSteamIDs );
+ bool BYieldingAddFreeLicense( const CSteamID & steamID, uint32 unPackageID, uint32 unIPPublic = 0, const char *pchStoreCountryCode = NULL );
+ int YieldingGrantGuestPass( const CSteamID & steamID, uint32 unPackageID, uint32 unPassesToGrant = 1, int32 nDaysToExpiration = -1 );
+
+ bool BSendGCMsgToClient( const CSteamID & steamIDTarget, const CGCMsgBase& msg );
+ bool BSendGCMsgToClient( const CSteamID & steamIDTarget, const CProtoBufMsgBase& msg );
+
+ //sends a message to the system (GCH or GC.exe)
+ bool BSendSystemMessage( const CGCMsgBase& msg, uint32 *pcubSent = NULL );
+ bool BSendSystemMessage( const CProtoBufMsgBase& msg, uint32 *pcubSent = NULL );
+ bool BSendSystemMessage( const ::google::protobuf::Message &msgOut, MsgType_t eSendMsg );
+
+ //utilities that will send a message and then wait for the specified reply. This will return false if no reply is sent in the default timeout, or the message or status
+ //don't match what is expected. This can only be called from within a job
+ bool BYldSendMessageAndGetReply( const CSteamID &steamIDTarget, CProtoBufMsgBase &msgOut, CProtoBufMsgBase *pMsgIn, MsgType_t eMsg );
+ //bool BYldSendGCMessageAndGetReply( int32 nGCDirIndex, CProtoBufMsgBase &msgOut, CProtoBufMsgBase *pMsgIn, MsgType_t eMsg );
+ bool BYldSendSystemMessageAndGetReply( CGCMsgBase &msgOut, CGCMsgBase *pMsgIn, MsgType_t eMsg );
+ bool BYldSendSystemMessageAndGetReply( CProtoBufMsgBase &msgOut, CProtoBufMsgBase *pMsgIn, MsgType_t eMsg );
+ bool BYldSendSystemMessageAndGetReply( const ::google::protobuf::Message &msgSend, MsgType_t eSendMsg, ::google::protobuf::Message *pMsgResponse, MsgType_t eRespondMsg );
+
+ bool BReplyToMessage( CGCMsgBase &msgOut, const CGCMsgBase &msgIn );
+ bool BReplyToMessage( CProtoBufMsgBase &msgOut, const CProtoBufMsgBase &msgIn );
+
+ //sending messages to clients or as responses with pre-serialized bodies so that the work to generate the message and body doesn't have to be done multiple times
+ bool BSendGCMsgToClientWithPreSerializedBody( const CSteamID & steamIDTarget, MsgType_t eMsgType, const CMsgProtoBufHeader& hdr, const byte *pubBody, uint32 cubBody ) const;
+ bool BSendGCMsgToSystemWithPreSerializedBody( MsgType_t eMsgType, const CMsgProtoBufHeader& hdr, const byte *pubBody, uint32 cubBody ) const;
+ bool BReplyToMessageWithPreSerializedBody( MsgType_t eMsgType, const CProtoBufMsgBase &msgIn, const byte *pubBody, uint32 cubBody ) const;
+
+ const char *GetPath() const { return m_sPath; }
+ virtual const char *GetSteamAPIKey();
+
+ void QueueStartPlaying( const CSteamID & steamID, const CSteamID & gsSteamID, uint32 unServerAddr, uint16 usServerPort, const uint8 *pubVarData, uint32 cubVarData );
+ void YieldingExecuteNextStartPlaying();
+ bool BIsInLogonSurge() const { return m_nLogonSurgeFramesRemaining > 0; }
+
+ /// Are we too busy to process any "low service level guarantee" WebAPI jobs?
+ bool BShouldThrottleLowServiceLevelWebAPIJobs() const;
+
+ /// Removes the entry in the start playing queue for the specified
+ /// Steam ID, if one exists. Returns true if it was found
+ /// and removed, false if no entry existed
+ bool BRemoveStartPlayingQueueEntry( const CSteamID & steamID );
+
+ void YieldingStopPlaying( const CSteamID & steamID );
+ void YieldingStopGameserver( const CSteamID & steamID );
+ bool BIsSOCacheBeingLoaded( const CSteamID & steamID ) const { return m_rbtreeSOCachesBeingLoaded.Find( steamID ) != m_rbtreeSOCachesBeingLoaded.InvalidIndex(); }
+
+ bool BYieldingLockSteamID( const CSteamID &steamID, const char *pszFilename, int nLineNum );
+ bool BYieldingLockSteamIDPair( const CSteamID &steamIDA, const CSteamID &steamIDB, const char *pszFilename, int nLineNum );
+ bool BLockSteamIDImmediate( const CSteamID &steamID );
+ void UnlockSteamID( const CSteamID &steamID );
+ bool IsSteamIDLocked( const CSteamID &steamID );
+ bool IsSteamIDLockedByJob( const CSteamID &steamID, const CJob *pJob ) const;
+ bool IsSteamIDLockedByCurJob( const CSteamID &steamID ) const;
+ bool IsSteamIDUnlockedOrLockedByCurJob( const CSteamID &steamID );
+ CJob *PJobHoldingLock( const CSteamID &steamID );
+
+ const CLock *FindSteamIDLock( const CSteamID &steamID );
+ void DumpSteamIDLocks( bool bFull, int nMax = 10 );
+ void DumpJobs( const char *pszJobName, int nMax, int nPrintLocksMax ) const;
+ void DumpJob( JobID_t jobID, int nPrintLocksMax ) const;
+ virtual void Dump() const;
+ void VerifySOCacheLRU();
+
+ void SetProfilingEnabled( bool bEnabled );
+ void SetDumpVprofImbalances( bool bEnabled );
+ bool GetVprofImbalances();
+
+ bool YieldingWritebackDirtyCaches( uint32 unSecondToDelayWrite );
+ void AddCacheToWritebackQueue( CGCSharedObjectCache *pSOCache );
+
+ bool BYieldingRetrieveCacheVersion( CGCSharedObjectCache *pSOCache );
+ void AddCacheToVersionChangedList( CGCSharedObjectCache *pSOCache );
+
+ const char *GetCDNURL() const;
+
+ struct GCMemcachedBuffer_t
+ {
+ const void *m_pubData;
+ int m_cubData;
+ };
+
+ struct GCMemcachedGetResult_t
+ {
+ bool m_bKeyFound; // true if the key was found
+ CUtlAllocation m_bufValue; // the value of the key
+ };
+
+ // Memcached access
+ bool BMemcachedSet( const char *pKey, const ::google::protobuf::Message &protoBufObj );
+ bool BMemcachedDelete( const char *pKey );
+ bool BYieldingMemcachedGet( const char *pKey, ::google::protobuf::Message &protoBufObj );
+ bool BMemcachedSet( const CUtlString &strKey, const CUtlBuffer &buf );
+ bool BMemcachedSet( const CUtlVector<CUtlString> &vecKeys, const CUtlVector<GCMemcachedBuffer_t> &vecValues );
+ bool BMemcachedDelete( const CUtlString &strKey );
+ bool BMemcachedDelete( const CUtlVector<CUtlString> &vecKeys );
+ bool BYieldingMemcachedGet( const CUtlString &strKey, GCMemcachedGetResult_t &result );
+ bool BYieldingMemcachedGet( const CUtlVector<CUtlString> &vecKeys, CUtlVector<GCMemcachedGetResult_t> &vecResults );
+
+ // IP location
+ bool BYieldingGetIPLocations( CUtlVector<uint32> &vecIPs, CUtlVector<CIPLocationInfo> &infos );
+
+ /// Check if any of the sessions don't have geolocation info, then fetch
+ /// the info we can. The supplied SteamID's may refer to individuals or
+ /// game servers
+ bool BYieldingUpdateGeoLocation( CUtlVector<CSteamID> const &requestedVecSteamIds );
+
+ // Stats
+ virtual void SystemStats_Update( CGCMsgGetSystemStatsResponse &msgStats );
+
+ //called to determine the amount of uptime this GC has had
+ uint32 GetGCUpTime() const;
+ RTime32 GetGCInitTime() const { return m_nInitTime; }
+
+protected:
+ virtual bool BYieldingLoadSOCache( CGCSharedObjectCache *pSOCache );
+ void RemoveSOCache( const CSteamID & steamID );
+
+ void AddCacheToLRU( CGCSharedObjectCache * pSOCache );
+ void RemoveCacheFromLRU( CGCSharedObjectCache * pSOCache );
+
+ void SetStartupComplete() { m_bStartupComplete = true; }
+private:
+
+ static void AssertCallbackFunc( const char *pchFile, int nLine, const char *pchMessage );
+ void UpdateSOCacheVersions();
+
+ //
+ // Create and initialize player / gameserver sessions. This should be called only from two places:
+ // - YieldingExecuteNextStartPlaying()
+ // - YieldingRequestSession(), which can be called ANY TIME we ask for a locked session but don't have one
+ //
+ void YieldingStartPlaying( const CSteamID & steamID, const CSteamID & gsSteamID, uint32 unServerAddr, uint16 usServerPort, CUtlBuffer *pVarData );
+ void YieldingStartGameserver( const CSteamID & steamID, uint32 unServerAddr, uint16 usServerPort, const uint8 *pubVarData, uint32 cubVarData );
+
+ void YieldingExecuteStartPlayingQueueEntryByIndex( int idxStartPlayingQueue );
+
+ // Base behaviors of the IGameCoordinator interface. These are not overridable.
+ // They each call the On* version below, which is overridable by subclasses
+ virtual bool BInit( AppId_t unAppID, const char *pchAppPath, IGameCoordinatorHost *pHost );
+ virtual bool BMainLoopOncePerFrame( uint64 ulLimitMicroseconds );
+ virtual bool BMainLoopUntilFrameCompletion( uint64 ulLimitMicroseconds );
+ virtual void Shutdown();
+ virtual void Uninit();
+ virtual void MessageFromClient( const CSteamID & senderID, uint32 unMsgType, void *pubData, uint32 cubData );
+ virtual void Validate( CValidator &validator, const char *pchName );
+ virtual void SQLResults( GID_t gidContextID );
+
+ static void SetUserSessionDetails( CGCUserSession *pUserSession, KeyValues *pkvDetails );
+
+ // Remembers the console spew func we overrode so we can restore it at uninit
+ SpewOutputFunc_t m_OutputFuncPrev;
+
+ // profiling
+ bool m_bStartProfiling;
+ bool m_bStopProfiling;
+ bool m_bDumpVprofImbalances;
+
+ //the time that the GC started so that we can compute uptime
+ RTime32 m_nInitTime;
+ RTime32 m_nStartupCompleteTime;
+
+ // local job handling
+ CJobMgr m_JobMgr;
+ CGCWGJobMgr m_wgJobMgr;
+
+ // session tracking
+ CTHash<CGCUserSession *, uint64> m_hashUserSessions;
+ CTHash<CGCGSSession *, uint64> m_hashGSSessions;
+ CUtlHashMapLarge< uint64, CGCGSSession* > m_mapGSSessionsByNetAddress;
+
+ // Shared object caches
+ CUtlHashMapLarge<CSteamID, CGCSharedObjectCache *> m_mapSOCache;
+ CUtlVector< CGCSharedObjectCache * >m_vecCacheWritebacks;
+ CUtlLinkedList< CSteamID, uint32> m_listCachesToUnload;
+ CUtlRBTree<CSteamID, int > m_rbtreeSOCachesBeingLoaded;
+ CUtlRBTree<CSteamID, int > m_rbtreeSOCachesWithDirtyVersions;
+ CUtlRBTree< AccountID_t, int32, CDefLess< AccountID_t > > m_rbFlushInventoryCacheAccounts;
+ JobID_t m_jobidFlushInventoryCacheAccounts;
+ uint32 m_numFlushInventoryCacheAccountsLastScheduled;
+
+ // steamID locks
+ CTHash<CLock, CSteamID> m_hashSteamIDLocks;
+
+ // Account Details
+ CAccountDetailsManager m_AccountDetailsManager;
+
+ // State
+ AppId_t m_unAppID;
+ CUtlString m_sPath;
+ IGameCoordinatorHost *m_pHost;
+ bool m_bStartupComplete;
+ bool m_bIsShuttingDown;
+
+ struct StartPlayingWork_t
+ {
+ CSteamID m_steamID;
+ CSteamID m_gsSteamID;
+ uint32 m_unServerAddr;
+ uint16 m_usServerPort;
+ CUtlBuffer *m_pVarData;
+ };
+ CUtlLinkedList< StartPlayingWork_t, int > m_llStartPlaying;
+ CUtlMap< CSteamID, int, int > m_mapStartPlayingQueueIndexBySteamID;
+
+ /// Number of active jobs
+ int m_nStartPlayingJobCount;
+
+ // URL to use for our app's CDN'd images
+ mutable CUtlString m_sCDNURL;
+
+ /// Number of active jobs currently inside YieldingRequestSession
+ int m_nRequestSessionJobsActive;
+
+ /// Number of frames to wait before checking to
+ /// see if we're done with logon surge. This will
+ /// be nonzero while in logon surge, and zero
+ /// if we're not in logon surge.
+ int m_nLogonSurgeFramesRemaining;
+
+ //rate limiting system
+ CSteamIDRateLimit m_MsgRateLimit;
+
+ //a map of the HTTP error messages so we can batch them up as they occur as they tend to be very spammy
+ void ReportHTTPError( const char* pszError, CGCEmitGroup::EMsgLevel eLevel );
+ void DumpHTTPErrors();
+ struct SHTTPError
+ {
+ CUtlString m_sStr;
+ uint32 m_nCount;
+ CGCEmitGroup::EMsgLevel m_eSeverity;
+ };
+ CUtlHashMapLarge< const char*, SHTTPError*, CaseSensitiveStrEquals, MurmurHash3ConstCharPtr > m_HTTPErrors;
+ CScheduledFunction< CGCBase > m_DumpHTTPErrorsSchedule;
+};
+
+extern CGCBase *GGCBase();
+
+// ----------------------------------------------------------------------------
+// BEGIN BLOCK OF PRE-TF-GC-SPLIT SCAFFOLDING
+// ----------------------------------------------------------------------------
+
+// Alright, here's why this mess is here: we (TF) are doing some work that depends
+// on work that Dota has done, things like the SQL message queue, etc. We want to
+// update our code so that it does the right thing and uses their functionaity so
+// that they can then integrate our changes.
+//
+// TF is way out of date, though, and so rather than make that work, some of which
+// has significant time pressure, dependent on a massive weeks-long integrate that
+// we aren't sure is the right thing to do anyway, we're going to bring over their
+// code as-is and then stub in implementations.
+//
+// This horrific template mess basically says "are we compiling in an environment
+// with a spit GC?" (specifically, does CGCBase::GetGCType() exist?). If so, call
+// into real implementations for things like GetGCGType(), etc. If not, feed back
+// sane default values (ie., we're a master GC and there's only one of us). Doing
+// this means that Dota can integrate our code and we can integrate Dota code and
+// no callsites have to change. If/when TF *does* split our GC, things will silently
+// update their implementation and then we can delete the scaffolding whenever.
+template < class tBaseGCType >
+struct IsSplitGC
+{
+ typedef char t_Yes[1];
+ typedef char t_No[2];
+
+ template < typename T, T >
+ struct InternalTypeCheck; \
+
+ template < typename T >
+ static t_Yes& Test( InternalTypeCheck< uint32(), &T::GetGCType> * );
+
+ template < typename T >
+ static t_No& Test( ... );
+
+ enum { kValue = sizeof( Test<tBaseGCType>( NULL ) ) == sizeof( t_Yes ) };
+};
+
+template < bool tTest, typename T > struct EnableIf { typedef T Type; };
+template < typename T > struct EnableIf< false, T > { };
+
+#define SplitVsSingleGCImpl( returntype_, functionandparams_, splitgcimpl_, singlegcimpl_ ) \
+ template < class tBaseGCType = CGCBase > typename EnableIf<IsSplitGC<tBaseGCType>::kValue, returntype_>::Type functionandparams_ { return splitgcimpl_; } \
+ template < class tBaseGCType = CGCBase > typename EnableIf<!IsSplitGC<tBaseGCType>::kValue, returntype_>::Type functionandparams_ { return singlegcimpl_; }
+
+SplitVsSingleGCImpl( uint32, GGetGCType(), GGCBase()->GetGCType(), 0 );
+SplitVsSingleGCImpl( uint32, GGetGCCountForType( uint32 unGCType ), GDirectory()->GetGCCountForType( unGCType ), 1 );
+SplitVsSingleGCImpl( uint32, GGetGCInstance(), GGCBase()->GetDirInfo()->GetInstance(), 0 );
+
+#undef SplitVsSingleGCImpl
+
+// ----------------------------------------------------------------------------
+// END BLOCK OF TEMPORARY PRE-INTEGRATE SCAFFOLDING
+// ----------------------------------------------------------------------------
+
+EResult YieldingSendWebAPIRequest( CSteamAPIRequest &request, KeyValues *pKVResponse, CUtlString &errMsg, bool b200MeansSuccess );
+
+/// Scope object that remembers if a Steam ID is locked, and automatically unlocks it upon destruction
+class CScopedSteamIDLock
+{
+public:
+
+ /// Create object without assigning SteamID
+ CScopedSteamIDLock() : m_steamID(), m_bLockedSuccesfully(false), m_iSavedLockCount(-1) {}
+
+ /// Construct object to lock a particular steam ID
+ CScopedSteamIDLock( const CSteamID& steamID ) : m_steamID( steamID ), m_bLockedSuccesfully(false), m_iSavedLockCount(-1) {}
+
+ /// Destructor performs an unlock, if we are holing a lock. That's pretty much the whole purpose of this class.
+ ~CScopedSteamIDLock() { Unlock(); }
+
+ /// Return true if we're locked
+ bool IsLocked() const { return m_bLockedSuccesfully; }
+
+ /// Lock the currently assigned Steam ID. (Set by constructor)
+ bool BYieldingPerformLock( const char *pszFilename, int nLineNumber )
+ {
+ Assert( !m_bLockedSuccesfully );
+ Unlock();
+
+ Assert( m_steamID.IsValid() );
+
+ m_bLockedSuccesfully = GGCBase()->BYieldingLockSteamID( m_steamID, pszFilename, nLineNumber );
+ if ( m_bLockedSuccesfully )
+ {
+ const CLock *pLock = GGCBase()->FindSteamIDLock( m_steamID );
+ if ( pLock )
+ {
+ m_iSavedLockCount = pLock->GetReferenceCount();
+ }
+ else
+ {
+ Assert( pLock );
+ }
+ }
+ return m_bLockedSuccesfully;
+ }
+
+ /// Lock the specified Steam ID.
+ bool BYieldingPerformLock( const CSteamID &steamID, const char *pszFilename, int nLineNumber )
+ {
+
+ // Should not already be locked, but unlock just in case
+ Assert( !m_bLockedSuccesfully );
+ Unlock();
+
+ // Attempt lock
+ m_steamID = steamID;
+ m_bLockedSuccesfully = GGCBase()->BYieldingLockSteamID( m_steamID, pszFilename, nLineNumber );
+ if ( m_bLockedSuccesfully )
+ {
+ const CLock *pLock = GGCBase()->FindSteamIDLock( m_steamID );
+ if ( pLock )
+ {
+ m_iSavedLockCount = pLock->GetReferenceCount();
+ }
+ else
+ {
+ Assert( pLock );
+ }
+ }
+
+ // Report status
+ return m_bLockedSuccesfully;
+ }
+
+ /// Mark us as already being locked. This is used when you already have a lock, and just want to use
+ /// the scoped class to do the unlock when you're done
+ void MarkLocked( const CSteamID &steamID )
+ {
+
+ // Should not already be locked, but unlock just in case
+ Assert( !m_bLockedSuccesfully );
+ Unlock();
+
+ // Remember state
+ m_steamID = steamID;
+ m_bLockedSuccesfully = true;
+ const CLock *pLock = GGCBase()->FindSteamIDLock( m_steamID );
+ if ( pLock )
+ {
+ m_iSavedLockCount = pLock->GetReferenceCount();
+ }
+ else
+ {
+ Assert( pLock );
+ }
+ }
+
+ /// Stop tracking that we're locking this object. This does *not* unlock anything -- Unlock does
+ /// that. This is for "we hit the end of our scope but now want to do something else with the
+ /// lock (ie., pass back to a caller)". This is like un-smarting a smart pointer.
+ void Release()
+ {
+ if ( m_bLockedSuccesfully )
+ {
+ VerifyPreUnlock();
+ m_bLockedSuccesfully = false;
+ m_iSavedLockCount = -1;
+ }
+ }
+
+ /// Unlock the lock, if we are holding it. Usually this is not called directly,
+ /// we let the destructor do it.
+ void Unlock()
+ {
+ if ( m_bLockedSuccesfully )
+ {
+ VerifyPreUnlock();
+ GGCBase()->UnlockSteamID( m_steamID );
+ m_bLockedSuccesfully = false;
+ m_iSavedLockCount = -1;
+ }
+ }
+
+private:
+ void VerifyPreUnlock() const
+ {
+ Assert( m_bLockedSuccesfully );
+
+ const CLock *pLock = GGCBase()->FindSteamIDLock( m_steamID );
+ if ( pLock )
+ {
+ AssertMsg1( m_iSavedLockCount == pLock->GetReferenceCount(), "Lock imbalance on %s detected by scope lock", pLock->GetName() );
+ }
+ else
+ {
+ Assert( pLock );
+ }
+ }
+
+private:
+ CSteamID m_steamID;
+ bool m_bLockedSuccesfully;
+ int m_iSavedLockCount;
+
+};
+
+/// Scope object that remembers if a specific lock is taken, and releases it
+class CScopedGenericLock
+{
+public:
+
+ /// Create object for specific lock SteamID
+ CScopedGenericLock( CLock &lock ) : m_pLock( &lock ), m_bLockedSuccesfully(false), m_iSavedLockCount(-1) {}
+
+ /// Destructor performs an unlock, if we are holing a lock. That's pretty much the whole purpose of this class.
+ ~CScopedGenericLock() { Unlock(); }
+
+ /// Return true if we're locked
+ bool IsLocked() const { return m_bLockedSuccesfully; }
+
+ /// Lock the current lock. (Set by constructor)
+ bool BYieldingPerformLock( const char *pszFilename, int nLineNumber )
+ {
+ Assert( !m_bLockedSuccesfully );
+ Unlock();
+
+ Assert( m_pLock );
+
+ m_bLockedSuccesfully = GJobCur()._BYieldingAcquireLock( m_pLock, pszFilename, nLineNumber );
+ if ( m_bLockedSuccesfully )
+ {
+ m_iSavedLockCount = m_pLock->GetReferenceCount();
+ }
+ return m_bLockedSuccesfully;
+ }
+
+ /// Stop tracking that we're locking this object. This does *not* unlock anything -- Unlock does
+ /// that. This is for "we hit the end of our scope but now want to do something else with the
+ /// lock (ie., pass back to a caller)". This is like un-smarting a smart pointer.
+ void Release()
+ {
+ if ( m_bLockedSuccesfully )
+ {
+ VerifyPreUnlock();
+ m_bLockedSuccesfully = false;
+ m_iSavedLockCount = -1;
+ }
+ }
+
+ /// Unlock the lock, if we are holding it. Usually this is not called directly,
+ /// we let the destructor do it.
+ void Unlock()
+ {
+ if ( m_bLockedSuccesfully )
+ {
+ VerifyPreUnlock();
+ if ( m_pLock->GetJobLocking() != &GJobCur() )
+ {
+ AssertMsg( false, "CScopedGenericLock::Unlock called when job %s doesn't own the lock", GJobCur().GetName() );
+ return;
+ }
+
+ GJobCur().ReleaseLock( m_pLock );
+ m_bLockedSuccesfully = false;
+ m_iSavedLockCount = -1;
+ }
+ }
+
+private:
+ void VerifyPreUnlock() const
+ {
+ Assert( m_bLockedSuccesfully );
+
+ AssertMsg1( m_iSavedLockCount == m_pLock->GetReferenceCount(), "Lock imbalance on %s detected by scope lock", m_pLock->GetName() );
+ }
+
+private:
+ CLock *m_pLock;
+ bool m_bLockedSuccesfully;
+ int m_iSavedLockCount;
+
+};
+
+class CTrustedHelper_AssertOnNonPublicUniverseFailures
+{
+public:
+ void operator()( const bool bExpResult, const char *pszExp ) const
+ {
+ if ( GetUniverse() != k_EUniversePublic )
+ {
+ AssertMsg1( bExpResult, "%s", pszExp );
+ }
+ }
+};
+
+class CVerifyIfTrustedHelper
+{
+public:
+ template < typename tTrustedCriteriaImpl >
+ CVerifyIfTrustedHelper( const tTrustedCriteriaImpl& CriteriaImpl, const bool bExpResult, const char *pszExp )
+ {
+ CommonConstructor( CriteriaImpl, bExpResult, pszExp );
+ }
+
+ // Helper constructor so we can do VerifyIfTrusted( pSomePointer ) without getting a type-
+ // conversion-to-bool warning.
+ template < typename tTrustedCriteriaImpl >
+ CVerifyIfTrustedHelper( const tTrustedCriteriaImpl& CriteriaImpl, const void *pointer, const char *pszExp )
+ {
+ CommonConstructor( CriteriaImpl, pointer != NULL, pszExp );
+ }
+
+ bool GetResult() const { return m_bExpResult; }
+
+private:
+ template < typename tTrustedCriteriaImpl >
+ void CommonConstructor( const tTrustedCriteriaImpl& CriteriaImpl, const bool bExpResult, const char *pszExp )
+ {
+ m_bExpResult = bExpResult;
+
+ CriteriaImpl( bExpResult, pszExp );
+ }
+
+ bool m_bExpResult;
+};
+
+// Examples of things that we don't want to assert on, even during testing:
+// - we got a message from someone who didn't have a session. This could be a Steam
+// problem, or we could be backlogged processing messages, or we could have an offline
+// trade get processed in the background.
+// - someone sent up a message involving an item that they don't own. (Same reasons as
+// a missing session.)
+//
+// Examples of things that we do want to assert on internally, but not when running public:
+// - our messages match contents fulfill criteria that the client specifies. ie., if we're
+// passing up a "victim" and a "killer" Steam ID, they won't be identical.
+// - our schema data is set up correctly. ie., necessary fields ("kill eater localization
+// string") that we expect to fail a schema parse init for if they're absent are present.
+// - our messages are coming from places that make sense. ie., a game server isn't sending
+// messages we expect to get from a client, or we aren't getting a message from a client
+// that we expect only to come from elsewhere in GC code.
+#define VerifyIfTrusted( exp_ ) \
+ CVerifyIfTrustedHelper( CTrustedHelper_AssertOnNonPublicUniverseFailures(), (exp_), #exp_ ).GetResult()
+
+// !KLUDGE! Shim to make it easier to merge over stuff from DOTA
+class CGCInterface
+{
+public:
+ CSteamID ConstructSteamIDForClient( AccountID_t unAccountID ) const;
+};
+extern CGCInterface *GGCInterface();
+
+} // namespace GCSDK
+
+struct CRatelimitedSpewController
+{
+public:
+ CRatelimitedSpewController() : m_rtCooldownStart( 0 ), m_rtLastSpew( 0 ), m_numSpewed( 0 ), m_numSkipped( 0 ) {}
+ ~CRatelimitedSpewController() {}
+
+ enum ERate_t
+ {
+ k_ERate_Skip = -1, // skip printing anything, rate limit too high
+ k_ERate_Print = 0, // print the full message
+ // default is to print how many messages total as well
+ };
+
+ int RegisterSpew();
+
+private:
+ RTime32 m_rtCooldownStart;
+ RTime32 m_rtLastSpew;
+ int m_numSpewed;
+ int m_numSkipped;
+};
+
+#define EmitErrorRatelimited( SPEW_GROUP_ID, fmtstring, ... ) \
+ static CRatelimitedSpewController s_spewcontroller; \
+ int nspewcontroller = s_spewcontroller.RegisterSpew(); \
+ switch ( nspewcontroller ) \
+ { \
+ case CRatelimitedSpewController::k_ERate_Skip: break; \
+ default: \
+ EmitError( SPEW_GROUP_ID, nspewcontroller \
+ ? fmtstring "(... and %d more skipped)\n" \
+ : fmtstring \
+ , __VA_ARGS__ \
+ , nspewcontroller \
+ ); \
+ break; \
+ } \
+
+
+#define EmitInfoRatelimited( SPEW_GROUP_ID, ConsoleLevel, LogLevel, fmtstring, ... ) \
+ static CRatelimitedSpewController s_spewcontroller; \
+ int nspewcontroller = s_spewcontroller.RegisterSpew(); \
+ switch ( nspewcontroller ) \
+ { \
+ case CRatelimitedSpewController::k_ERate_Skip: break; \
+ default: \
+ EmitInfo( SPEW_GROUP_ID, ConsoleLevel, LogLevel, nspewcontroller \
+ ? fmtstring "(... and %d more skipped)\n" \
+ : fmtstring \
+ , __VA_ARGS__ \
+ , nspewcontroller \
+ ); \
+ break; \
+ } \
+
+
+#define EG_WARNING_RATELIMITED( SPEW_GROUP_ID, fmtstring, ... ) \
+ static CRatelimitedSpewController s_spewcontroller; \
+ int nspewcontroller = s_spewcontroller.RegisterSpew(); \
+ switch ( nspewcontroller ) \
+ { \
+ case CRatelimitedSpewController::k_ERate_Skip: break; \
+ default: \
+ EG_WARNING( SPEW_GROUP_ID, nspewcontroller \
+ ? fmtstring "(... and %d more skipped)\n" \
+ : fmtstring \
+ , __VA_ARGS__ \
+ , nspewcontroller \
+ ); \
+ break; \
+ } \
+
+
+
+#endif // GCBASE_H
diff --git a/public/gcsdk/gcclient.h b/public/gcsdk/gcclient.h
new file mode 100644
index 0000000..3724897
--- /dev/null
+++ b/public/gcsdk/gcclient.h
@@ -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 \ No newline at end of file
diff --git a/public/gcsdk/gcclient_sharedobjectcache.h b/public/gcsdk/gcclient_sharedobjectcache.h
new file mode 100644
index 0000000..8edcc55
--- /dev/null
+++ b/public/gcsdk/gcclient_sharedobjectcache.h
@@ -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
diff --git a/public/gcsdk/gcclientjob.h b/public/gcsdk/gcclientjob.h
new file mode 100644
index 0000000..8e22876
--- /dev/null
+++ b/public/gcsdk/gcclientjob.h
@@ -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
diff --git a/public/gcsdk/gcclientsdk.h b/public/gcsdk/gcclientsdk.h
new file mode 100644
index 0000000..0fa7e07
--- /dev/null
+++ b/public/gcsdk/gcclientsdk.h
@@ -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
diff --git a/public/gcsdk/gcconstants.h b/public/gcsdk/gcconstants.h
new file mode 100644
index 0000000..bbaf068
--- /dev/null
+++ b/public/gcsdk/gcconstants.h
@@ -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
diff --git a/public/gcsdk/gcdirtyfield.h b/public/gcsdk/gcdirtyfield.h
new file mode 100644
index 0000000..283bfad
--- /dev/null
+++ b/public/gcsdk/gcdirtyfield.h
@@ -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
diff --git a/public/gcsdk/gchost.h b/public/gcsdk/gchost.h
new file mode 100644
index 0000000..00640c9
--- /dev/null
+++ b/public/gcsdk/gchost.h
@@ -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 \ No newline at end of file
diff --git a/public/gcsdk/gcinterface.h b/public/gcsdk/gcinterface.h
new file mode 100644
index 0000000..f29f760
--- /dev/null
+++ b/public/gcsdk/gcinterface.h
@@ -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
diff --git a/public/gcsdk/gcjob.h b/public/gcsdk/gcjob.h
new file mode 100644
index 0000000..b404531
--- /dev/null
+++ b/public/gcsdk/gcjob.h
@@ -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
diff --git a/public/gcsdk/gcleaderboardapi.h b/public/gcsdk/gcleaderboardapi.h
new file mode 100644
index 0000000..473d16a
--- /dev/null
+++ b/public/gcsdk/gcleaderboardapi.h
@@ -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
diff --git a/public/gcsdk/gclogger.h b/public/gcsdk/gclogger.h
new file mode 100644
index 0000000..b543214
--- /dev/null
+++ b/public/gcsdk/gclogger.h
@@ -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
diff --git a/public/gcsdk/gcmsg.h b/public/gcsdk/gcmsg.h
new file mode 100644
index 0000000..49eea6e
--- /dev/null
+++ b/public/gcsdk/gcmsg.h
@@ -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 \ No newline at end of file
diff --git a/public/gcsdk/gcparalleljobfarm.h b/public/gcsdk/gcparalleljobfarm.h
new file mode 100644
index 0000000..5ffb847
--- /dev/null
+++ b/public/gcsdk/gcparalleljobfarm.h
@@ -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
diff --git a/public/gcsdk/gcreportprinter.h b/public/gcsdk/gcreportprinter.h
new file mode 100644
index 0000000..78a6b00
--- /dev/null
+++ b/public/gcsdk/gcreportprinter.h
@@ -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
diff --git a/public/gcsdk/gcschema.h b/public/gcsdk/gcschema.h
new file mode 100644
index 0000000..65ffd7b
--- /dev/null
+++ b/public/gcsdk/gcschema.h
@@ -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 \ No newline at end of file
diff --git a/public/gcsdk/gcsdk.h b/public/gcsdk/gcsdk.h
new file mode 100644
index 0000000..5867d3f
--- /dev/null
+++ b/public/gcsdk/gcsdk.h
@@ -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
diff --git a/public/gcsdk/gcsdk_auto.h b/public/gcsdk/gcsdk_auto.h
new file mode 100644
index 0000000..a899e4e
--- /dev/null
+++ b/public/gcsdk/gcsdk_auto.h
@@ -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
diff --git a/public/gcsdk/gcsession.h b/public/gcsdk/gcsession.h
new file mode 100644
index 0000000..e2ff636
--- /dev/null
+++ b/public/gcsdk/gcsession.h
@@ -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
diff --git a/public/gcsdk/gcsqlquery.h b/public/gcsdk/gcsqlquery.h
new file mode 100644
index 0000000..4a6fb1b
--- /dev/null
+++ b/public/gcsdk/gcsqlquery.h
@@ -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
diff --git a/public/gcsdk/gcsqlwritequeue.h b/public/gcsdk/gcsqlwritequeue.h
new file mode 100644
index 0000000..a8141d4
--- /dev/null
+++ b/public/gcsdk/gcsqlwritequeue.h
@@ -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
+
diff --git a/public/gcsdk/gcsteamdefines.h b/public/gcsdk/gcsteamdefines.h
new file mode 100644
index 0000000..be9fb29
--- /dev/null
+++ b/public/gcsdk/gcsteamdefines.h
@@ -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 \ No newline at end of file
diff --git a/public/gcsdk/gcsystemaccess.h b/public/gcsdk/gcsystemaccess.h
new file mode 100644
index 0000000..235932a
--- /dev/null
+++ b/public/gcsdk/gcsystemaccess.h
@@ -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
diff --git a/public/gcsdk/gcsystemmsgs.h b/public/gcsdk/gcsystemmsgs.h
new file mode 100644
index 0000000..a6304cd
--- /dev/null
+++ b/public/gcsdk/gcsystemmsgs.h
@@ -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
diff --git a/public/gcsdk/gcwebapi.h b/public/gcsdk/gcwebapi.h
new file mode 100644
index 0000000..2ffa164
--- /dev/null
+++ b/public/gcsdk/gcwebapi.h
@@ -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
diff --git a/public/gcsdk/gcwebapikey.h b/public/gcsdk/gcwebapikey.h
new file mode 100644
index 0000000..286b31b
--- /dev/null
+++ b/public/gcsdk/gcwebapikey.h
@@ -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
diff --git a/public/gcsdk/gcwgjobmgr.h b/public/gcsdk/gcwgjobmgr.h
new file mode 100644
index 0000000..f656183
--- /dev/null
+++ b/public/gcsdk/gcwgjobmgr.h
@@ -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 \ No newline at end of file
diff --git a/public/gcsdk/http.h b/public/gcsdk/http.h
new file mode 100644
index 0000000..b766ca2
--- /dev/null
+++ b/public/gcsdk/http.h
@@ -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
diff --git a/public/gcsdk/job.h b/public/gcsdk/job.h
new file mode 100644
index 0000000..99bc076
--- /dev/null
+++ b/public/gcsdk/job.h
@@ -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
diff --git a/public/gcsdk/jobmgr.h b/public/gcsdk/jobmgr.h
new file mode 100644
index 0000000..93b6387
--- /dev/null
+++ b/public/gcsdk/jobmgr.h
@@ -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
diff --git a/public/gcsdk/jobtime.h b/public/gcsdk/jobtime.h
new file mode 100644
index 0000000..afbf5aa
--- /dev/null
+++ b/public/gcsdk/jobtime.h
@@ -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
diff --git a/public/gcsdk/messagelist.h b/public/gcsdk/messagelist.h
new file mode 100644
index 0000000..e48fc54
--- /dev/null
+++ b/public/gcsdk/messagelist.h
@@ -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 \ No newline at end of file
diff --git a/public/gcsdk/msgbase.h b/public/gcsdk/msgbase.h
new file mode 100644
index 0000000..3850914
--- /dev/null
+++ b/public/gcsdk/msgbase.h
@@ -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
diff --git a/public/gcsdk/msgprotobuf.h b/public/gcsdk/msgprotobuf.h
new file mode 100644
index 0000000..0d51003
--- /dev/null
+++ b/public/gcsdk/msgprotobuf.h
@@ -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
diff --git a/public/gcsdk/netpacket.h b/public/gcsdk/netpacket.h
new file mode 100644
index 0000000..f3ba033
--- /dev/null
+++ b/public/gcsdk/netpacket.h
@@ -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
diff --git a/public/gcsdk/netpacketpool.h b/public/gcsdk/netpacketpool.h
new file mode 100644
index 0000000..e78fdc8
--- /dev/null
+++ b/public/gcsdk/netpacketpool.h
@@ -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
diff --git a/public/gcsdk/protobufsharedobject.h b/public/gcsdk/protobufsharedobject.h
new file mode 100644
index 0000000..71050ea
--- /dev/null
+++ b/public/gcsdk/protobufsharedobject.h
@@ -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
diff --git a/public/gcsdk/refcount.h b/public/gcsdk/refcount.h
new file mode 100644
index 0000000..ceaa1aa
--- /dev/null
+++ b/public/gcsdk/refcount.h
@@ -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
diff --git a/public/gcsdk/scheduledfunction.h b/public/gcsdk/scheduledfunction.h
new file mode 100644
index 0000000..1522de7
--- /dev/null
+++ b/public/gcsdk/scheduledfunction.h
@@ -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
diff --git a/public/gcsdk/schemasharedobject.h b/public/gcsdk/schemasharedobject.h
new file mode 100644
index 0000000..bfd85cb
--- /dev/null
+++ b/public/gcsdk/schemasharedobject.h
@@ -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
diff --git a/public/gcsdk/sdocache.h b/public/gcsdk/sdocache.h
new file mode 100644
index 0000000..850aef3
--- /dev/null
+++ b/public/gcsdk/sdocache.h
@@ -0,0 +1,1190 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Serialized Digital Object caching and manipulation
+//
+//=============================================================================
+
+#ifndef SBOCACHE_H
+#define SBOCACHE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tier1/utlhashmaplarge.h"
+#include "tier1/utlqueue.h"
+#include "tier1/utlvector.h"
+#include <algorithm>
+
+namespace GCSDK
+{
+// Call to register SDOs. All SDO types must be registered before loaded
+#define REG_SDO( classname ) GSDOCache().RegisterSDO( classname::k_eType, #classname )
+
+// A string used to tell the difference between nil objects and actual objects in memcached
+extern const char k_rgchNilObjSerializedValue[];
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Keeps a moving average of a data set
+//-----------------------------------------------------------------------------
+template< int SAMPLES >
+class CMovingAverage
+{
+public:
+ CMovingAverage()
+ {
+ Reset();
+ }
+
+ void Reset()
+ {
+ memset( m_rglSamples, 0, sizeof( m_rglSamples ) );
+ m_cSamples = 0;
+ m_lTotal = 0;
+ }
+
+ void AddSample( int64 lSample )
+ {
+ int iIndex = m_cSamples % SAMPLES;
+ m_lTotal += ( lSample - m_rglSamples[iIndex] );
+ m_rglSamples[iIndex] = lSample;
+ m_cSamples++;
+ }
+
+ uint64 GetAveragedSample() const
+ {
+ if ( !m_cSamples )
+ return 0;
+
+ int64 iMax = (int64)MIN( m_cSamples, SAMPLES );
+ return m_lTotal / iMax;
+ }
+
+private:
+ int64 m_rglSamples[SAMPLES];
+ int64 m_lTotal;
+ uint64 m_cSamples;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Global accessor to the manager
+//-----------------------------------------------------------------------------
+class CSDOCache;
+CSDOCache &GSDOCache();
+
+
+//-----------------------------------------------------------------------------
+// Purpose: interface to a Database Backed Object
+//-----------------------------------------------------------------------------
+class ISDO
+{
+public:
+ virtual ~ISDO() {}
+
+ // Identification
+ virtual int GetType() const = 0;
+ virtual uint32 GetHashCode() const = 0;
+ virtual bool IsEqual( const ISDO *pSDO ) const = 0;
+
+ // Ref counting
+ virtual int AddRef() = 0;
+ virtual int Release() = 0;
+ virtual int GetRefCount() = 0;
+
+ // memory usage
+ virtual size_t CubBytesUsed() = 0;
+
+ // Serialization tools
+ virtual bool BReadFromBuffer( const byte *pubData, int cubData ) = 0;
+ virtual void WriteToBuffer( CUtlBuffer &memBuffer ) = 0;
+
+ // memcached batching tools
+ virtual void GetMemcachedKeyName( CFmtStr &strDest ) = 0;
+
+ // SQL loading
+ virtual bool BYldLoadFromSQL( CUtlVector<ISDO *> &vecSDOToLoad, CUtlVector<bool> &vecResults ) const = 0;
+
+ // post-load initialization (whether loaded from SQL or memcached)
+ virtual void PostLoadInit() = 0;
+
+ // comparison function for validating memcached copies vs SQL copies
+ virtual bool IsIdentical( ISDO *pSDO ) = 0;
+
+ // Returns true if this is not the actual object, but a placeholder to remember that this object
+ // doesn't actually exist
+ virtual bool BNilObject() const = 0;
+
+ // Allocs an SDO that must return true for IsEqual( this ) and BNilObject(). This is so we can
+ // cache load attempts for objects that don't exist
+ virtual ISDO *AllocNilObject() = 0;
+
+ // Creates a key name that looks like "Prefix_%u" but faster
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: base class for a Serialized Digital Object
+//-----------------------------------------------------------------------------
+template<typename KeyType, int eSDOType, class ProtoMsg>
+class CBaseSDO : public ISDO
+{
+public:
+ typedef KeyType KeyType_t;
+ enum { k_eType = eSDOType };
+
+ CBaseSDO( const KeyType &key ) : m_Key( key ), m_nRefCount( 0 ) {}
+
+ const KeyType &GetKey() const { return m_Key; }
+
+ // ISDO implementation
+ virtual int AddRef();
+ virtual int Release();
+ virtual int GetRefCount();
+ virtual int GetType() const { return eSDOType; }
+ virtual bool BNilObject() const { return false; }
+ virtual uint32 GetHashCode() const;
+ virtual bool BReadFromBuffer( const byte *pubData, int cubData );
+ virtual void WriteToBuffer( CUtlBuffer &memBuffer );
+
+ // Implement this in a subclass if there's some value like 0 or -1 that's invalid so
+ // the system can know to not even attempt to load it
+ static bool BKeyValid( const KeyType_t &key ) { return true; }
+
+ // We use protobufs for all serialization
+ virtual void SerializeToProtobuf( ProtoMsg &msg ) = 0;
+ virtual bool DeserializeFromProtobuf( const ProtoMsg &msg ) = 0;
+
+ // default comparison function - override to do your own compare
+ virtual bool IsEqual( const ISDO *pSDO ) const;
+
+ // default load from SQL is no-op as not all types have permanent storage - override to create a
+ // batch load
+ virtual bool BYldLoadFromSQL( CUtlVector<ISDO *> &vecSDOToLoad, CUtlVector<bool> &vecResults ) const;
+
+ // override to do initialization after load
+ virtual void PostLoadInit() {}
+
+ // compares the serialized versions by default. Override to have more specific behavior
+ virtual bool IsIdentical( ISDO *pSDO );
+
+ // Creates a copy of the object with the same key
+ virtual ISDO *AllocNilObject();
+
+ // tools
+ bool WriteToMemcached();
+ bool DeleteFromMemcached();
+
+ // Creates a key name that looks like "Prefix_%u" but faster. Also makes sure the correct buffer size is passed
+ template <size_t prefixBufSize>
+ void CreateSimpleMemcachedName( CFmtStr &strDest, char (&rgchPrefix)[prefixBufSize], uint32 unSuffix )
+ {
+ CSDOCache::CreateSimpleMemcachedName( strDest, rgchPrefix, prefixBufSize - 1, unSuffix );
+ }
+
+private:
+ int m_nRefCount;
+ KeyType m_Key;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Represents an object we tried to load but didn't exist. We use
+// this to cache the failure so we don't keep trying to look it up
+//-----------------------------------------------------------------------------
+template<typename KeyType, int eSDOType, class ProtoMsg>
+class CNilSDO : public CBaseSDO<KeyType, eSDOType, ProtoMsg>
+{
+public:
+ CNilSDO( const KeyType &key, const char *pchMemcachedKeyName ) : CBaseSDO<KeyType, eSDOType, ProtoMsg>( key ), m_sMemcachedKeyName( pchMemcachedKeyName ) {}
+
+ virtual bool BNilObject() const { return true; }
+ virtual bool BReadFromBuffer( const byte *pubData, int cubData ) { return false; }
+ virtual void WriteToBuffer( CUtlBuffer &memBuffer ) { memBuffer.PutString( k_rgchNilObjSerializedValue ); }
+ virtual void SerializeToProtobuf( ProtoMsg &msg ) {}
+ virtual bool DeserializeFromProtobuf( const ProtoMsg &msg ) { return false; }
+ virtual size_t CubBytesUsed() { return sizeof( *this ) + m_sMemcachedKeyName.Length(); }
+ virtual void GetMemcachedKeyName( CFmtStr &strKey ) { V_strncpy( strKey.Access(), m_sMemcachedKeyName, FMTSTR_STD_LEN ); }
+private:
+ CUtlString m_sMemcachedKeyName;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: references to a database-backed object
+// maintains refcount of the object
+//-----------------------------------------------------------------------------
+template<class T>
+class CSDORef
+{
+ T *m_pSDO;
+public:
+ CSDORef() { m_pSDO = NULL; }
+ explicit CSDORef( CSDORef<T> &SDORef ) { m_pSDO = SDORef.Get(); if ( m_pSDO ) m_pSDO->AddRef(); }
+ explicit CSDORef( T *pSDO ) { m_pSDO = pSDO; if ( m_pSDO ) m_pSDO->AddRef(); }
+ ~CSDORef() { if ( m_pSDO ) m_pSDO->Release(); }
+
+ T *Get() { return m_pSDO; }
+ const T *Get() const { return m_pSDO; }
+
+ T *operator->() { return Get(); }
+ const T *operator->() const { return Get(); }
+
+ operator const T *() const { return m_pSDO; }
+ operator const T *() { return m_pSDO; }
+ operator T *() { return m_pSDO; }
+
+ CSDORef<T> &operator=( T *pSDO ) { if ( m_pSDO ) m_pSDO->Release(); m_pSDO = pSDO; if ( m_pSDO ) m_pSDO->AddRef(); return *this; }
+
+ bool operator !() const { return Get() == NULL; }
+
+ bool IsValid( void ) const { return Get() != NULL; }
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: manages a cache of SDO objects
+//-----------------------------------------------------------------------------
+class CSDOCache
+{
+public:
+ CSDOCache();
+ ~CSDOCache();
+
+ // Call to register SDOs. All SDO types must be registered before loaded
+ void RegisterSDO( int nType, const char *pchName );
+
+ // A struct to hold stats for the system. This is generated code in Steam. It would be great to make
+ // it generated code here if we could bring Steam's operational stats system in the GC
+ struct StatsSDOCache_t
+ {
+ uint64 m_cItemsLRUd;
+ uint64 m_cBytesLRUd;
+ uint64 m_cItemsUnreferenced;
+ uint64 m_cBytesUnreferenced;
+ uint64 m_cItemsInCache;
+ uint64 m_cBytesInCacheEst;
+ uint64 m_cItemsQueuedToLoad;
+ uint64 m_cItemsLoadedFromMemcached;
+ uint64 m_cItemsLoadedFromSQL;
+ uint64 m_cItemsFailedLoadFromSQL;
+ uint64 m_cQueuedMemcachedRequests;
+ uint64 m_cQueuedSQLRequests;
+ uint64 m_nSQLBatchSizeAvgx100;
+ uint64 m_nMemcachedBatchSizeAvgx100;
+ uint64 m_cSQLRequestsRejectedTooBusy;
+ uint64 m_cMemcachedRequestsRejectedTooBusy;
+ uint64 m_cNilItemsLoadedFromMemcached;
+ uint64 m_cNilItemsLoadedFromSQL;
+ };
+
+ // loads a SDO, and assigns a reference to it
+ // returns false if the item couldn't be loaded, or timed out loading
+ template<class T>
+ bool BYldLoadSDO( CSDORef<T> *pPSDORef, const typename T::KeyType_t &key, bool *pbTimeoutLoading = NULL );
+
+ // gets access to a SDO, but only if it's currently loaded
+ template<class T>
+ bool BGetLoadedSDO( CSDORef<T> *pPSDORef, const typename T::KeyType_t &key, bool *pbFoundNil = NULL );
+
+ // starts loading a SDO you're going to reference soon with the above BYldLoadSDO()
+ // use this to batch up requests, hinting a set then getting reference to a set is significantly faster
+ template<class T>
+ void HintLoadSDO( const typename T::KeyType_t &key );
+ // as above, but starts load a set
+ template<class T>
+ void HintLoadSDO( const CUtlVector<typename T::KeyType_t> &vecKeys );
+
+ // Clears a nil object if one exists for this key.
+ template<class T>
+ void RemoveNil( const typename T::KeyType_t &key );
+
+ // force a deletes a SDO from the cache - waits until the object is not referenced
+ template<class T>
+ bool BYldDeleteSDO( const typename T::KeyType_t &key, uint64 unMicrosecondsToWaitForUnreferenced );
+
+ // SDO refcount management
+ void OnSDOReferenced( ISDO *pSDO );
+ void OnSDOReleased( ISDO *pSDO );
+
+ // writes a SDO to memcached immediately
+ bool WriteSDOToMemcached( ISDO *pSDO );
+ // delete the SDO record from memcached
+ bool DeleteSDOFromMemcached( ISDO *pSDO );
+
+ // job results
+ void OnSDOLoadSuccess( int eSDO, int iRequestID, bool bNilObj, ISDO **ppSDO );
+ void OnMemcachedSDOLoadFailure( int eSDO, int iRequestID );
+ void OnSQLSDOLoadFailure( int eSDO, int iRequestID, bool bSQLLayerSucceeded );
+ void OnMemcachedLoadJobComplete( JobID_t jobID );
+ void OnSQLLoadJobComplete( int eSDO, JobID_t jobID );
+
+ // test access - deletes all unreferenced objects
+ void Flush();
+
+ // stats access
+ StatsSDOCache_t &GetStats() { return m_StatsSDOCache; }
+ int CubReferencedEst(); // number of bytes referenced in the cache
+
+ // prints info about the class
+ void Dump();
+
+ static void CreateSimpleMemcachedName( CFmtStr &strDest, const char *pchPrefix, uint32 unPrefixLen, uint32 unSuffix );
+
+ // memcached verification - returns the number of mismatches
+//**tempcomment** void YldVerifyMemcachedData( CreateSDOFunc_t pCreateSDOFunc, CUtlVector<uint32> &vecIDs, int *pcMatches, int *pcMismatches );
+
+#ifdef DBGFLAG_VALIDATE
+ void Validate( CValidator &validator, const char *pchName );
+#endif
+
+ // Functions that need to be in the frame loop
+ virtual bool BFrameFuncRunJobsUntilCompleted( CLimitTimer &limitTimer );
+ virtual bool BFrameFuncRunMemcachedQueriesUntilCompleted( CLimitTimer &limitTimer );
+ virtual bool BFrameFuncRunSQLQueriesUntilCompleted( CLimitTimer &limitTimer );
+
+private:
+ // Custom comparator for our hash map
+ class CDefPISDOEquals
+ {
+ public:
+ CDefPISDOEquals() {}
+ CDefPISDOEquals( int i ) {}
+ inline bool operator()( const ISDO *lhs, const ISDO *rhs ) const { return ( lhs->IsEqual( rhs ) ); }
+ inline bool operator!() const { return false; }
+ };
+
+ class CPISDOHashFunctor
+ {
+ public:
+ uint32 operator()(const ISDO *pSDO ) const { return pSDO->GetHashCode(); }
+ };
+
+ template<class T>
+ int FindLoadedSDO( const typename T::KeyType_t &key );
+
+ template<class T>
+ int QueueLoad( const typename T::KeyType_t &key );
+ int QueueMemcachedLoad( ISDO *pSDO );
+
+ // items already loaded - Maps the SDO to the LRU position
+ CUtlHashMapLarge<ISDO *, int, CDefPISDOEquals, CPISDOHashFunctor> m_mapISDOLoaded;
+
+ // items we have queued to load, in the state of either being loaded from memcached or SQL
+ // maps SDO to a list of jobs waiting on the load
+ CUtlHashMapLarge<ISDO *, CCopyableUtlVector<JobID_t>, CDefPISDOEquals, CPISDOHashFunctor> m_mapQueuedRequests;
+
+ // requests to load from memcached
+ CUtlLinkedList<int, int> m_queueMemcachedRequests;
+
+ // Jobs currently processing memcached load requests
+ CUtlVector<JobID_t> m_vecMemcachedJobs;
+
+ // Loading from SQL is divided by SDO type
+ struct SQLRequestManager_t
+ {
+ // requests to load from SQL. Maps to an ID in the map of queued requests
+ CUtlLinkedList<int, int> m_queueRequestIDsToLoadFromSQL;
+
+ // SQL jobs we have active doing reads for cache items
+ CUtlVector<JobID_t> m_vecSQLJobs;
+ };
+
+ // a queue of requests to load from SQL for each type
+ CUtlHashMapLarge<int, SQLRequestManager_t *> m_mapQueueSQLRequests;
+
+ // jobs to wake up, since we've satisfied their SDO load request
+ struct JobToWake_t
+ {
+ JobID_t m_jobID;
+ bool m_bLoadLayerSuccess;
+ };
+ CUtlLinkedList<JobToWake_t, int> m_queueJobsToContinue;
+
+ struct LRUItem_t
+ {
+ ISDO * m_pSDO;
+ size_t m_cub;
+ };
+ CUtlLinkedList<LRUItem_t, int> m_listLRU;
+ uint32 m_cubLRUItems;
+ void RemoveSDOFromLRU( int iMapSDOLoaded );
+
+ struct TypeStats_t
+ {
+ TypeStats_t()
+ : m_nLoaded( 0 )
+ , m_nRefed( 0 )
+ , m_cubUnrefed( 0 )
+ , m_nNilObjects( 0 )
+ {}
+
+ CUtlString m_strName;
+ int m_nLoaded;
+ int m_nRefed;
+ int m_cubUnrefed;
+ int m_nNilObjects;
+ };
+
+ StatsSDOCache_t m_StatsSDOCache;
+ CMovingAverage<100> m_StatMemcachedBatchSize, m_StatSQLBatchSize;
+ CUtlMap<int, TypeStats_t> m_mapTypeStats;
+};
+
+
+//-----------------------------------------------------------------------------
+// Definition of CBaseSDO template functions now that CSDOCache is defined and
+// GSDOCache() can safely be used.
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// Purpose: adds a reference
+//-----------------------------------------------------------------------------
+template<typename KeyType, int ESDOType, class ProtoMsg>
+int CBaseSDO<KeyType,ESDOType,ProtoMsg>::AddRef()
+{
+ if ( ++m_nRefCount == 1 )
+ GSDOCache().OnSDOReferenced( this );
+
+ return m_nRefCount;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: releases a reference
+//-----------------------------------------------------------------------------
+template<typename KeyType, int ESDOType, class ProtoMsg>
+int CBaseSDO<KeyType,ESDOType,ProtoMsg>::Release()
+{
+ DbgVerify( m_nRefCount > 0 );
+
+ int nRefCount = --m_nRefCount;
+
+ if ( nRefCount == 0 )
+ GSDOCache().OnSDOReleased( this );
+
+ return nRefCount;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: ref count
+//-----------------------------------------------------------------------------
+template<typename KeyType, int ESDOType, class ProtoMsg>
+int CBaseSDO<KeyType,ESDOType,ProtoMsg>::GetRefCount()
+{
+ return m_nRefCount;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Hashes the object for insertion into a hashtable
+//-----------------------------------------------------------------------------
+template<typename KeyType, int ESDOType, class ProtoMsg>
+uint32 CBaseSDO<KeyType,ESDOType,ProtoMsg>::GetHashCode() const
+{
+#pragma pack( push, 1 )
+ struct hashcode_t
+ {
+ int m_Type;
+ KeyType_t m_Key;
+ } hashStruct = { ESDOType, m_Key };
+#pragma pack( pop )
+
+ return PearsonsHashFunctor<hashcode_t>()( hashStruct );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Deserializes the object
+//-----------------------------------------------------------------------------
+template<typename KeyType, int ESDOType, class ProtoMsg>
+bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::BReadFromBuffer( const byte *pubData, int cubData )
+{
+ ProtoMsg msg;
+ if ( !msg.ParseFromArray( pubData, cubData ) )
+ return false;
+
+ if ( !DeserializeFromProtobuf( msg ) )
+ return false;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Serializes the object
+//-----------------------------------------------------------------------------
+template<typename KeyType, int ESDOType, class ProtoMsg>
+void CBaseSDO<KeyType,ESDOType,ProtoMsg>::WriteToBuffer( CUtlBuffer &memBuffer )
+{
+ ProtoMsg *pMsg = CProtoBufMsg<ProtoMsg>::AllocProto();
+
+ SerializeToProtobuf( *pMsg );
+ uint32 unSize = pMsg->ByteSize();
+ memBuffer.EnsureCapacity( memBuffer.Size() + unSize );
+ pMsg->SerializeWithCachedSizesToArray( (uint8*)memBuffer.Base() + memBuffer.TellPut() );
+ memBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, memBuffer.TellPut() + unSize );
+
+ CProtoBufMsg<ProtoMsg>::FreeProto( pMsg );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: does an immediate write of the object to memcached
+//-----------------------------------------------------------------------------
+template<typename KeyType, int ESDOType, class ProtoMsg>
+bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::WriteToMemcached()
+{
+ return GSDOCache().WriteSDOToMemcached( this );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: does an immediate write of the object to memcached
+//-----------------------------------------------------------------------------
+template<typename KeyType, int ESDOType, class ProtoMsg>
+bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::DeleteFromMemcached()
+{
+ return GSDOCache().DeleteSDOFromMemcached( this );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: default equality function - compares type and key
+//-----------------------------------------------------------------------------
+template<typename KeyType, int ESDOType, class ProtoMsg>
+bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::IsEqual( const ISDO *pSDO ) const
+{
+ if ( GetType() != pSDO->GetType() )
+ return false;
+
+ return ( GetKey() == static_cast<const CBaseSDO<KeyType,ESDOType,ProtoMsg> *>( pSDO )->GetKey() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Batch load a group of SDO's of the same type from SQL.
+// Default is no-op as not all types have permanent storage.
+//-----------------------------------------------------------------------------
+template<typename KeyType, int ESDOType, class ProtoMsg>
+bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::BYldLoadFromSQL( CUtlVector<ISDO *> &vecSDOToLoad, CUtlVector<bool> &vecResults ) const
+{
+ FOR_EACH_VEC( vecResults, i )
+ {
+ vecResults[i] = true;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: default validation function - compares serialized versions
+//-----------------------------------------------------------------------------
+bool CompareSDOObjects( ISDO *pSDO1, ISDO *pSDO2 );
+
+template<typename KeyType, int ESDOType, class ProtoMsg>
+bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::IsIdentical( ISDO *pSDO )
+{
+ return CompareSDOObjects( this, pSDO );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: default validation function - compares serialized versions
+//-----------------------------------------------------------------------------
+template<typename KeyType, int ESDOType, class ProtoMsg>
+ISDO *CBaseSDO<KeyType,ESDOType,ProtoMsg>::AllocNilObject()
+{
+ CFmtStr strKey;
+ GetMemcachedKeyName( strKey );
+ return new CNilSDO<KeyType,ESDOType,ProtoMsg>( GetKey(), strKey );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds a loaded SDO in memory. Returns the index of the object
+// into the loaded SDOs map
+//-----------------------------------------------------------------------------
+template<class T>
+int CSDOCache::FindLoadedSDO( const typename T::KeyType_t &key )
+{
+ // see if we have it in cache first
+ T probe( key );
+ return m_mapISDOLoaded.Find( &probe );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Queues loading an SDO. Returns the index of the entry in the
+// load queue
+//-----------------------------------------------------------------------------
+template<class T>
+int CSDOCache::QueueLoad( const typename T::KeyType_t &key )
+{
+ T probe( key );
+ int iMap = m_mapQueuedRequests.Find( &probe );
+ if ( m_mapQueuedRequests.IsValidIndex( iMap ) )
+ return iMap;
+
+ return QueueMemcachedLoad( new T( key ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Preloads the object into the local cache
+//-----------------------------------------------------------------------------
+template<class T>
+void CSDOCache::HintLoadSDO( const typename T::KeyType_t &key )
+{
+ // see if this is something we should even try to load
+ if ( !T::BKeyValid( key ) )
+ return;
+
+ // see if we have it in cache first
+ if ( !m_mapISDOLoaded.IsValidIndex( FindLoadedSDO<T>( key ) ) )
+ {
+ QueueLoad<T>( key );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Preloads a set set of objects into the local cache
+//-----------------------------------------------------------------------------
+template<class T>
+void CSDOCache::HintLoadSDO( const CUtlVector<typename T::KeyType_t> &vecKeys )
+{
+ FOR_EACH_VEC( vecKeys, i )
+ {
+ HintLoadSDO<T>( vecKeys[i] );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns an already-loaded SDO
+//-----------------------------------------------------------------------------
+template<class T>
+bool CSDOCache::BGetLoadedSDO( CSDORef<T> *pPSDORef, const typename T::KeyType_t &key, bool *pbFoundNil )
+{
+ if ( NULL != pbFoundNil )
+ {
+ *pbFoundNil = false;
+ }
+
+ int iMap = FindLoadedSDO<T>( key );
+ if ( !m_mapISDOLoaded.IsValidIndex( iMap ) )
+ return false;
+
+ ISDO *pObj = m_mapISDOLoaded.Key( iMap );
+ if ( pObj->BNilObject() )
+ {
+ int iLRU = m_mapISDOLoaded[ iMap ];
+ Assert( m_listLRU.IsValidIndex( iLRU ) );
+ if ( m_listLRU.IsValidIndex( iLRU ) )
+ {
+ // Even though we don't return nil objects, this is a hit on it
+ // so it needs to go to the back of the LRU
+ m_listLRU.LinkToTail( m_mapISDOLoaded[ iMap ] );
+ }
+
+ if ( NULL != pbFoundNil )
+ {
+ *pbFoundNil = true;
+ }
+
+ return false;
+ }
+ else
+ {
+ *pPSDORef = assert_cast<T*>( pObj );
+ return true;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Loads the object into memory
+//-----------------------------------------------------------------------------
+template<class T>
+bool CSDOCache::BYldLoadSDO( CSDORef<T> *pPSDORef, const typename T::KeyType_t &key, bool *pbTimeoutLoading /* = NULL */ )
+{
+ VPROF_BUDGET( "CSDOCache::BYldLoadSDO", VPROF_BUDGETGROUP_STEAM );
+ if ( pbTimeoutLoading )
+ *pbTimeoutLoading = false;
+
+ // Clear the current object the ref is holding
+ *pPSDORef = NULL;
+
+ // see if this is something we should even try to load
+ if ( !T::BKeyValid( key ) )
+ return false;
+
+ // see if we have it in cache first
+ bool bFoundNil = false;
+ if ( BGetLoadedSDO( pPSDORef, key, &bFoundNil ) )
+ return true;
+
+ // If we've already tried to look this up in the past don't bother looking it up again
+ if ( bFoundNil )
+ return false;
+
+ // otherwise batch it for load
+ int iMap = QueueLoad<T>( key );
+
+ // make sure we could queue it
+ if ( !m_mapQueuedRequests.IsValidIndex( iMap ) )
+ return false;
+
+ // add the current job to this list waiting for the object to load
+ m_mapQueuedRequests[iMap].AddToTail( GJobCur().GetJobID() );
+
+ // wait for it to load (loader will signal our job when done)
+ if ( !GJobCur().BYieldingWaitForWorkItem() )
+ {
+ if ( pbTimeoutLoading )
+ *pbTimeoutLoading = true;
+ return false;
+ }
+
+ // should be loaded - look up in the load map and try again
+ bool bRet = BGetLoadedSDO( pPSDORef, key );
+ //Assert( bRet );
+
+ return bRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Clears a nil object if one exists for this key.
+//-----------------------------------------------------------------------------
+template<class T>
+void CSDOCache::RemoveNil( const typename T::KeyType_t &key )
+{
+ // See if this key is allowed to exist
+ if ( !T::BKeyValid( key ) )
+ {
+ AssertMsg( false, "RemoveNil called with an invalid key" );
+ return;
+ }
+
+ // Remove the nil if there is one
+ int iMap = FindLoadedSDO< T >( key );
+ if ( m_mapISDOLoaded.IsValidIndex( iMap ) )
+ {
+ ISDO *pSDO = m_mapISDOLoaded.Key( iMap );
+ if ( !pSDO->BNilObject() )
+ return;
+
+ int iMapStats = m_mapTypeStats.Find( pSDO->GetType() );
+ if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
+ {
+ m_mapTypeStats[iMapStats].m_nNilObjects--;
+ }
+
+ RemoveSDOFromLRU( iMap );
+ m_mapISDOLoaded.RemoveAt( iMap );
+ delete pSDO;
+ }
+
+ // Remove the object from memcached. Do this here because while we have for sure removed any nil that was in memory
+ // this may have been a nil that was not in memory but instead cached in memcached.
+ T temp( key );
+ DeleteSDOFromMemcached( &temp );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: reloads an existing element from the SQL DB
+//-----------------------------------------------------------------------------
+template<class T>
+bool CSDOCache::BYldDeleteSDO( const typename T::KeyType_t &key, uint64 unMicrosecondsToWaitForUnreferenced )
+{
+ // see if we have it in cache first
+ int iMap = FindLoadedSDO<T>( key );
+ if ( !m_mapISDOLoaded.IsValidIndex( iMap ) )
+ {
+ T temp( key );
+ temp.DeleteFromMemcached();
+ return true; /* we're good, it's not loaded */
+ }
+
+ assert_cast<T *>(m_mapISDOLoaded.Key( iMap ))->DeleteFromMemcached();
+
+ // check the ref count
+ int64 cAttempts = MAX( 1LL, (int64)(unMicrosecondsToWaitForUnreferenced / k_cMicroSecPerShellFrame) );
+ while ( cAttempts-- > 0 )
+ {
+ if ( 0 == m_mapISDOLoaded.Key( iMap )->GetRefCount() )
+ {
+ // delete the object
+ Assert( m_listLRU.IsValidIndex( m_mapISDOLoaded[iMap] ) );
+
+ RemoveSDOFromLRU( iMap );
+ ISDO *pSDO = m_mapISDOLoaded.Key( iMap );
+ m_mapISDOLoaded.RemoveAt( iMap );
+
+ int iMapStats = m_mapTypeStats.Find( m_mapISDOLoaded.Key( iMap )->GetType() );
+ if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
+ {
+ if ( pSDO->BNilObject() )
+ {
+ m_mapTypeStats[iMapStats].m_nNilObjects--;
+ }
+ else
+ {
+ m_mapTypeStats[iMapStats].m_nLoaded--;
+ }
+ }
+
+ delete pSDO;
+ return true;
+ }
+ else
+ {
+ GJobCur().BYieldingWaitOneFrame();
+ }
+ }
+
+ // couldn't reload
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: A class to factor out the common code in most SDO SQL loading funcitons
+//-----------------------------------------------------------------------------
+template<class T>
+class CSDOSQLLoadHelper
+{
+public:
+
+ //this is the results of the load helper, abstracted to provide a more efficient means to lookup the queries without
+ //having to do very expensive memory management
+ template< class SCH >
+ class CResults
+ {
+ public:
+
+ //given a key, this will return the range of indices that include this key. -1 will be set to the start if no match is found. This will return
+ //the number of matches found
+ int GetKeyIndexRange( typename T::KeyType_t Key, int& nStart, int& nEnd ) const;
+
+ //given a key, this will return the first schema that matches this key, or NULL if none is found. Only use this if you know there
+ //is a maximum of a single value
+ const SCH* GetSingleResultForKey( typename T::KeyType_t Key ) const;
+
+ //used to start iteration over a group of results. Given a key, this will return the index that can be used to get the result or -1 if no match is found
+ int GetFirstResultIndex( typename T::KeyType_t Key ) const;
+ //given an index that was previously obtained from GetFirst/NextResultIndex, this will get the next result that has the same key, or return -1 if no further keys of that type are found
+ int GetNextResultIndex( int nOldResultIndex ) const;
+ //given an index from GetFirst/NextResultIndex that is valid (i.e. not -1), this will return the result that was obtained
+ const SCH* GetResultFromIndex( int nIndex ) const;
+ //given an index returned by the GetFirst/NextResultIndex, this will determine if it is valid
+ static int InvalidIndex() { return -1; }
+
+ private:
+ //given a key, this maps to the specific result
+ template< class TKeyType >
+ struct SKeyToResult
+ {
+ bool operator< (const SKeyToResult< TKeyType >& rhs ) const { return m_Key < rhs.m_Key; }
+ typename TKeyType m_Key; //key value
+ uint32 m_nResultIndex; //index into our result list
+ };
+
+ friend class CSDOSQLLoadHelper;
+
+ CUtlVector< SKeyToResult< typename T::KeyType_t > > m_KeyToResult;
+ CUtlVector< SCH > m_Results;
+ };
+
+
+ // Initializes with the vector of objects being loaded
+ CSDOSQLLoadHelper( const CUtlVector<ISDO *> *vecSDOToLoad, const char *pchProfileName );
+
+ // Loads all rows in the SCH table whose field nFieldID match the key of an SDO being loaded
+ template<class SCH>
+ bool BYieldingExecuteSingleTable( int nFieldID, CResults< SCH >& Results );
+ // Loads the specified columns for all rows in the SCH table whose field nFieldID match the key of an SDO being loaded
+ template<class SCH>
+ bool BYieldingExecuteSingleTable( int nFieldID, const CColumnSet &csetRead, CResults< SCH >& Results );
+
+ // Functions to load rows from more than one table at a time
+
+ // Loads all rows in the SCH table whose field nFieldID match the key of an SDO being loaded
+ template<class SCH>
+ void AddTableToQuery( int nFieldID );
+
+ // Loads the specified columns for all rows in the SCH table whose field nFieldID match the key of an SDO being loaded
+ template<class SCH>
+ void AddTableToQuery( int nFieldID, const CColumnSet &csetRead );
+
+ // Executes the mutli-table query
+ bool BYieldingExecute();
+
+ // Gets the results for a table from a multi-table query
+ template<class SCH>
+ bool BGetResults( int nQuery, CResults< SCH >& Results );
+
+private:
+ CUtlVector<typename T::KeyType_t> m_vecKeys;
+ CSQLAccess m_sqlAccess;
+
+ struct Query_t
+ {
+ Query_t( const CColumnSet &columnSet, int nKeyCol ) : m_ColumnSet( columnSet ), m_nKeyCol( nKeyCol ) {}
+ CColumnSet m_ColumnSet;
+ int m_nKeyCol;
+ };
+
+ CUtlVector<Query_t> m_vecQueries;
+};
+
+//utility to help with iteration through a SQL load result list
+#define FOR_EACH_SQL_LOAD_RESULT( Results, Key, Index ) for( int Index = Results.GetFirstResultIndex( Key ); Index != Results.InvalidIndex(); Index = Results.GetNextResultIndex( Index ) )
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor. Initializes with the vector of objects being loaded
+//-----------------------------------------------------------------------------
+template<class T>
+CSDOSQLLoadHelper<T>::CSDOSQLLoadHelper( const CUtlVector<ISDO *> *vecSDOToLoad, const char *pchProfileName )
+ : m_vecKeys( 0, vecSDOToLoad->Count() )
+{
+ FOR_EACH_VEC( *vecSDOToLoad, i )
+ {
+ m_vecKeys.AddToTail( ( (T*)vecSDOToLoad->Element( i ) )->GetKey() );
+ }
+
+ Assert( m_vecKeys.Count() > 0 );
+ DbgVerify( m_sqlAccess.BBeginTransaction( pchProfileName ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Loads all rows in the SCH table whose field nFieldID match the
+// key of an SDO being loaded.
+//-----------------------------------------------------------------------------
+template<class T>
+template<class SCH>
+bool CSDOSQLLoadHelper<T>::BYieldingExecuteSingleTable( int nFieldID, CResults< SCH >& Results )
+{
+ static const CColumnSet cSetRead = CColumnSet::Full<SCH>();
+ return BYieldingExecuteSingleTable( nFieldID, cSetRead, Results );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Loads the specified columns for all rows in the SCH table whose
+// field nFieldID match the key of an SDO being loaded
+//-----------------------------------------------------------------------------
+template<class T>
+template<class SCH>
+bool CSDOSQLLoadHelper<T>::BYieldingExecuteSingleTable( int nFieldID, const CColumnSet &csetRead, CResults< SCH >& Results )
+{
+ AddTableToQuery<SCH>( nFieldID, csetRead );
+ if ( !BYieldingExecute() )
+ return false;
+
+ return BGetResults<SCH>( 0, Results );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Loads all rows in the SCH table whose field nFieldID match the key
+// of an SDO being loaded
+//-----------------------------------------------------------------------------
+template<class T>
+template<class SCH>
+void CSDOSQLLoadHelper<T>::AddTableToQuery( int nFieldID )
+{
+ static const CColumnSet cSetRead = CColumnSet::Full<SCH>();
+ AddTableToQuery<SCH>( nFieldID, cSetRead );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Loads the specified columns for all rows in the SCH table whose
+// field nFieldID match the key of an SDO being loaded
+//-----------------------------------------------------------------------------
+template<class T>
+template<class SCH>
+void CSDOSQLLoadHelper<T>::AddTableToQuery( int nFieldID, const CColumnSet &csetRead )
+{
+ Assert( csetRead.GetRecordInfo() == GSchemaFull().GetSchema( SCH::k_iTable ).GetRecordInfo() );
+
+ // Bind the params
+ FOR_EACH_VEC( m_vecKeys, i )
+ {
+ m_sqlAccess.AddBindParam( m_vecKeys[i] );
+ }
+
+ // Build the query
+ CUtlString sCommand;
+ {
+ TSQLCmdStr sSelect;
+ const char *pchColumnName = GSchemaFull().GetSchema( SCH::k_iTable ).GetRecordInfo()->GetColumnInfo( nFieldID ).GetName();
+
+ BuildSelectStatementText( &sSelect, csetRead );
+ sCommand.Format( "%s WHERE %s IN (%.*s)", sSelect.Access(), pchColumnName, GetInsertArgStringChars( m_vecKeys.Count() ), GetInsertArgString() );
+ }
+
+ // Execute. Because we're in a transaction this will delay to the commit
+ DbgVerify( m_sqlAccess.BYieldingExecute( NULL, sCommand ) );
+
+ m_vecQueries.AddToTail( Query_t( csetRead, nFieldID ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Executes the mutli-table query
+//-----------------------------------------------------------------------------
+template<class T>
+bool CSDOSQLLoadHelper<T>::BYieldingExecute()
+{
+ if ( 0 == m_vecKeys.Count() )
+ {
+ m_sqlAccess.RollbackTransaction();
+ return false;
+ }
+
+ if ( !m_sqlAccess.BCommitTransaction() )
+ return false;
+
+ Assert( (uint32)m_vecQueries.Count() == m_sqlAccess.GetResultSetCount() );
+ return (uint32)m_vecQueries.Count() == m_sqlAccess.GetResultSetCount();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets the results for a table from a multi-table query
+//-----------------------------------------------------------------------------
+template<class T>
+template<class SCH>
+bool CSDOSQLLoadHelper<T>::BGetResults( int nQuery, CResults< SCH >& Results )
+{
+ //clear any previous results
+ Results.m_KeyToResult.Purge();
+ Results.m_Results.Purge();
+
+ IGCSQLResultSetList *pSQLResults = m_sqlAccess.GetResults();
+ Assert( pSQLResults && nQuery >= 0 && (uint32)nQuery < pSQLResults->GetResultSetCount() && pSQLResults->GetResultSetCount() == (uint32)m_vecQueries.Count() );
+ if ( NULL == pSQLResults || nQuery < 0 || (uint32)nQuery >= pSQLResults->GetResultSetCount() || pSQLResults->GetResultSetCount() != (uint32)m_vecQueries.Count() )
+ return false;
+
+ Assert( m_vecQueries[nQuery].m_ColumnSet.GetRecordInfo()->GetTableID() == SCH::k_iTable );
+ if ( m_vecQueries[nQuery].m_ColumnSet.GetRecordInfo()->GetTableID() != SCH::k_iTable )
+ return false;
+
+ //copy the results from the SQL queries over to our result list
+ Results.m_Results.EnsureCapacity( pSQLResults->GetResultSet( nQuery )->GetRowCount() );
+ if ( !CopyResultToSchVector( pSQLResults->GetResultSet( nQuery ), m_vecQueries[nQuery].m_ColumnSet, &Results.m_Results ) )
+ return false;
+
+ // Make a map that counts maps from our results into a sorted list for fast lookup
+ Results.m_KeyToResult.SetSize( Results.m_Results.Count() );
+ FOR_EACH_VEC( Results.m_Results, nCurrResult )
+ {
+ //get our key value
+ uint8 *pubData;
+ uint32 cubData;
+ if ( !Results.m_Results[ nCurrResult ].BGetField( m_vecQueries[nQuery].m_nKeyCol, &pubData, &cubData ) )
+ return false;
+
+ Assert( cubData == sizeof( T::KeyType_t ) );
+ if ( cubData != sizeof( T::KeyType_t ) )
+ {
+ Results.m_KeyToResult.Purge();
+ Results.m_Results.Purge();
+ return false;
+ }
+
+ //setup our record
+ Results.m_KeyToResult[ nCurrResult ].m_Key = *((T::KeyType_t *)pubData);
+ Results.m_KeyToResult[ nCurrResult ].m_nResultIndex = nCurrResult;
+ }
+
+ //sort for binary search capabilities
+ std::sort( Results.m_KeyToResult.begin(), Results.m_KeyToResult.end() );
+
+ return true;
+}
+
+template< typename T >
+template< typename SCH >
+int CSDOSQLLoadHelper< T >::CResults< SCH >::GetKeyIndexRange( typename T::KeyType_t Key, int& nStart, int& nEnd ) const
+{
+ //find the start of the range
+ nStart = GetFirstResultIndex( Key );
+ if( nStart == InvalidIndex() )
+ {
+ nEnd = InvalidIndex();
+ return 0;
+ }
+ //expand the end as long as it lies on a key in range and with the same value
+ for( nEnd = nStart + 1; ( nEnd < m_KeyToResult.Count() ) && ( m_KeyToResult[ nEnd ].m_Key == Key ); nEnd++ )
+ {
+ }
+ //and return the resulting number of elements we found
+ return nEnd - nStart;
+}
+
+
+template< typename T >
+template< typename SCH >
+const SCH* CSDOSQLLoadHelper< T >::CResults< SCH >::GetSingleResultForKey( typename T::KeyType_t Key ) const
+{
+ int nIndex = GetFirstResultIndex( Key );
+ AssertMsg( ( nIndex == InvalidIndex() ) || ( GetNextResultIndex( nIndex ) == InvalidIndex() ), "Requested a result from a SQL load helper assuming it was a singular entry, but found multiple instances of it" );
+ return GetResultFromIndex( nIndex );
+}
+
+template< typename T >
+template< typename SCH >
+int CSDOSQLLoadHelper< T >::CResults< SCH >::GetFirstResultIndex( typename T::KeyType_t Key ) const
+{
+ //dummy entry to compare against
+ SKeyToResult< typename T::KeyType_t > SearchKey;
+ SearchKey.m_Key = Key;
+
+ //binary search to find our index
+ const SKeyToResult< typename T::KeyType_t >* pMatch = std::lower_bound( m_KeyToResult.begin(), m_KeyToResult.end(), SearchKey );
+ //see if we found a match
+ if( ( pMatch == m_KeyToResult.end() ) || ( pMatch->m_Key != Key ) )
+ return InvalidIndex();
+
+ return ( pMatch - m_KeyToResult.begin() );
+}
+
+template< typename T >
+template< typename SCH >
+int CSDOSQLLoadHelper< T >::CResults< SCH >::GetNextResultIndex( int nOldResultIndex ) const
+{
+ //handle out of range elements. Either invalid, or the last element or beyond in our array
+ if( ( nOldResultIndex < 0 ) || ( nOldResultIndex + 1 >= m_KeyToResult.Count() ) )
+ return InvalidIndex();
+
+ //see if we are less than the next value in the list. If so, we aren't equal and need to stop iteration
+ if( m_KeyToResult[ nOldResultIndex ] < m_KeyToResult[ nOldResultIndex + 1 ] )
+ return InvalidIndex();
+
+ //same key for the next element, so return a match
+ return nOldResultIndex + 1;
+}
+
+template< typename T >
+template< typename SCH >
+const SCH* CSDOSQLLoadHelper< T >::CResults< SCH >::GetResultFromIndex( int nIndex ) const
+{
+ //ensure that the index is in range
+ if( ( nIndex < 0 ) || ( nIndex >= m_KeyToResult.Count() ) )
+ return NULL;
+ return &( m_Results[ m_KeyToResult[ nIndex ].m_nResultIndex ] );
+}
+
+} // namespace GCSDK
+
+#endif // SDOCACHE_H
diff --git a/public/gcsdk/sharedobject.h b/public/gcsdk/sharedobject.h
new file mode 100644
index 0000000..d1bfdd3
--- /dev/null
+++ b/public/gcsdk/sharedobject.h
@@ -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
diff --git a/public/gcsdk/sharedobjectcache.h b/public/gcsdk/sharedobjectcache.h
new file mode 100644
index 0000000..755035a
--- /dev/null
+++ b/public/gcsdk/sharedobjectcache.h
@@ -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
diff --git a/public/gcsdk/sharedobjecttransaction.h b/public/gcsdk/sharedobjecttransaction.h
new file mode 100644
index 0000000..da16c74
--- /dev/null
+++ b/public/gcsdk/sharedobjecttransaction.h
@@ -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
diff --git a/public/gcsdk/sqlaccess/columnset.h b/public/gcsdk/sqlaccess/columnset.h
new file mode 100644
index 0000000..f61aa5b
--- /dev/null
+++ b/public/gcsdk/sqlaccess/columnset.h
@@ -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
diff --git a/public/gcsdk/sqlaccess/record.h b/public/gcsdk/sqlaccess/record.h
new file mode 100644
index 0000000..38dd507
--- /dev/null
+++ b/public/gcsdk/sqlaccess/record.h
@@ -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
diff --git a/public/gcsdk/sqlaccess/recordinfo.h b/public/gcsdk/sqlaccess/recordinfo.h
new file mode 100644
index 0000000..a4d332a
--- /dev/null
+++ b/public/gcsdk/sqlaccess/recordinfo.h
@@ -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
diff --git a/public/gcsdk/sqlaccess/schema.h b/public/gcsdk/sqlaccess/schema.h
new file mode 100644
index 0000000..b6ea819
--- /dev/null
+++ b/public/gcsdk/sqlaccess/schema.h
@@ -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
diff --git a/public/gcsdk/sqlaccess/schemafull.h b/public/gcsdk/sqlaccess/schemafull.h
new file mode 100644
index 0000000..c971478
--- /dev/null
+++ b/public/gcsdk/sqlaccess/schemafull.h
@@ -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
diff --git a/public/gcsdk/sqlaccess/schemaupdate.h b/public/gcsdk/sqlaccess/schemaupdate.h
new file mode 100644
index 0000000..17cff47
--- /dev/null
+++ b/public/gcsdk/sqlaccess/schemaupdate.h
@@ -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
diff --git a/public/gcsdk/sqlaccess/sqlaccess.h b/public/gcsdk/sqlaccess/sqlaccess.h
new file mode 100644
index 0000000..2ea576b
--- /dev/null
+++ b/public/gcsdk/sqlaccess/sqlaccess.h
@@ -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
diff --git a/public/gcsdk/sqlaccess/sqlrecord.h b/public/gcsdk/sqlaccess/sqlrecord.h
new file mode 100644
index 0000000..e0846d0
--- /dev/null
+++ b/public/gcsdk/sqlaccess/sqlrecord.h
@@ -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
diff --git a/public/gcsdk/sqlaccess/sqlutil.h b/public/gcsdk/sqlaccess/sqlutil.h
new file mode 100644
index 0000000..5cad4d3
--- /dev/null
+++ b/public/gcsdk/sqlaccess/sqlutil.h
@@ -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
diff --git a/public/gcsdk/webapi_response.h b/public/gcsdk/webapi_response.h
new file mode 100644
index 0000000..1257efc
--- /dev/null
+++ b/public/gcsdk/webapi_response.h
@@ -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 \ No newline at end of file
diff --git a/public/gcsdk/workthreadpool.h b/public/gcsdk/workthreadpool.h
new file mode 100644
index 0000000..feaa3f4
--- /dev/null
+++ b/public/gcsdk/workthreadpool.h
@@ -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