//====== 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 #include "steammessages.pb.h" #include #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 *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 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( 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 CProtoBufPoolObj { private: TMsg *m_pMsg; private: // Disallow copying/assignment CProtoBufPoolObj( CProtoBufPoolObj const &x ); CProtoBufPoolObj & operator = ( CProtoBufPoolObj const &x ); public: CProtoBufPoolObj() { m_pMsg = CProtoBufMsg::AllocProto(); } ~CProtoBufPoolObj() { CProtoBufMsg::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