diff options
Diffstat (limited to 'public/gcsdk/msgprotobuf.h')
| -rw-r--r-- | public/gcsdk/msgprotobuf.h | 473 |
1 files changed, 473 insertions, 0 deletions
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 |