diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /gcsdk/gcclient.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'gcsdk/gcclient.cpp')
| -rw-r--r-- | gcsdk/gcclient.cpp | 601 |
1 files changed, 601 insertions, 0 deletions
diff --git a/gcsdk/gcclient.cpp b/gcsdk/gcclient.cpp new file mode 100644 index 0000000..01eba37 --- /dev/null +++ b/gcsdk/gcclient.cpp @@ -0,0 +1,601 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Holds the CGCClient class +// +//============================================================================= + +#include "stdafx.h" +#include "gcclient.h" +#include "steam/isteamgamecoordinator.h" +#include "gcsdk_gcmessages.pb.h" + +namespace GCSDK +{ + +//#define SOCDebug(...) Msg( __VA_ARGS__ ) +#define SOCDebug(...) ((void)0) + +//------------------------------------------------------------------------------ +// Purpose: Constructor +//------------------------------------------------------------------------------ +CGCClient::CGCClient( ISteamGameCoordinator *pSteamGameCoordinator, bool bGameserver ) +: m_pSteamGameCoordinator( NULL ), + m_memMsg( 0, 1024 ), +#ifndef STEAM + m_callbackGCMessageAvailable( NULL, NULL ), +#endif + m_mapSOCache( DefLessFunc(CSteamID) ) +{ +#ifndef STEAM + if( bGameserver ) + { + m_callbackGCMessageAvailable.SetGameserverFlag(); + } +#endif + if( pSteamGameCoordinator ) + { + DbgVerify( BInit( pSteamGameCoordinator ) ); + } +} + + +//------------------------------------------------------------------------------ +// Purpose: Constructor +//------------------------------------------------------------------------------ +CGCClient::~CGCClient( ) +{ + Uninit(); + + FOR_EACH_MAP_FAST( m_mapSOCache, i ) + { + delete m_mapSOCache[i]; + } + m_mapSOCache.RemoveAll(); +} + + +//------------------------------------------------------------------------------ +// Purpose: Performs the every-frame work required by the GC Client. Mostly that +// means running yielding jobs. +// Inputs: ulLimitMicroseconds - The target number of microseconds worth of +// work to do this time through the loop. +// Outputs: Returns true if there is still work to do that was skipped because +// time ran out. +//------------------------------------------------------------------------------ +bool CGCClient::BMainLoop( uint64 ulLimitMicroseconds, uint64 ulFrameTimeMicroseconds ) +{ + // Don't do any work if not initialized + if ( !m_pSteamGameCoordinator ) + return false; + + CLimitTimer limitTimer; + limitTimer.SetLimit( ulLimitMicroseconds ); + CJobTime::UpdateJobTime( ulFrameTimeMicroseconds ? ulFrameTimeMicroseconds : k_cMicroSecPerShellFrame ); + + bool bWorkRemaining = m_JobMgr.BFrameFuncRunSleepingJobs( limitTimer ); + bWorkRemaining |= m_JobMgr.BFrameFuncRunYieldingJobs( limitTimer ); + return bWorkRemaining; +} + + +//------------------------------------------------------------------------------ +// Purpose: Sends a message to the GC +// Inputs: unMsgType - the type ID of the message to send +// pubData - The data for the message we're sending +// cubData - The number of bytes of data in this message including any +// variable-lengthed data. +// Outputs: Returns false if the send failed. A return value of true doesn't +// mean that the message was necessarily received by the GC just that +// it didn't fail in obvious ways on the client. +//------------------------------------------------------------------------------ +bool CGCClient::BSendMessage( uint32 unMsgType, const uint8 *pubData, uint32 cubData ) +{ + if( m_pSteamGameCoordinator ) + return m_pSteamGameCoordinator->SendMessage( unMsgType, pubData, cubData ) == k_EGCResultOK; + else + return false; +} + + +//------------------------------------------------------------------------------ +// Purpose: Sends a message to the GC +// Inputs: msg - The message to send +// Outputs: Returns false if the send failed. A return value of true doesn't +// mean that the message was necessarily received by the GC just that +// it didn't fail in obvious ways on the client. +//------------------------------------------------------------------------------ +bool CGCClient::BSendMessage( const CGCMsgBase& msg ) +{ + return BSendMessage( msg.Hdr().m_eMsg, msg.PubPkt() + sizeof(GCMsgHdr_t), msg.CubPkt() - sizeof(GCMsgHdr_t) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Used to send protobuf messages to the GC +//----------------------------------------------------------------------------- +class CProtoBufGCClientSendHandler : public CProtoBufMsgBase::IProtoBufSendHandler +{ +public: + CProtoBufGCClientSendHandler( CGCClient *pGCClient ) + : m_pClient( pGCClient ) {} + virtual bool BAsyncSend( MsgType_t eMsg, const uint8 *pubMsgBytes, uint32 cubSize ) + { + g_theMessageList.TallySendMessage( eMsg & ~k_EMsgProtoBufFlag, cubSize ); + VPROF_BUDGET( "CGCClient", VPROF_BUDGETGROUP_STEAM ); + { + VPROF_BUDGET( "CGCClient - BSendGCMsgToClient (ProtoBuf)", VPROF_BUDGETGROUP_STEAM ); + return m_pClient->BSendMessage( eMsg | k_EMsgProtoBufFlag, pubMsgBytes, cubSize ); + } + } + +private: + CGCClient *m_pClient; +}; + + +//----------------------------------------------------------------------------- +// Purpose: Sends a message to the given SteamID +//----------------------------------------------------------------------------- +bool CGCClient::BSendMessage( const CProtoBufMsgBase& msg ) +{ + CProtoBufGCClientSendHandler sender( this ); + return msg.BAsyncSend( sender ); +} + + +//------------------------------------------------------------------------------ +// Purpose: Callback handler for the GCMessageAvailable_t callback. Handles +// incoming messages. +// Inputs: pCallback - the callback from Steam +//------------------------------------------------------------------------------ +void CGCClient::OnGCMessageAvailable( GCMessageAvailable_t *pCallback ) +{ + uint32 cubData; + uint32 unMsgType; + while( m_pSteamGameCoordinator && m_pSteamGameCoordinator->IsMessageAvailable( &cubData ) ) + { + // Get the size of the full message. sizeof( GCMsgHdr_t ) was not sent in the binary data + uint32 unFullSize = cubData + sizeof( GCMsgHdr_t ); + m_memMsg.EnsureCapacity( unFullSize ); + uint8 *pFullPacket = m_memMsg.Base(); + uint8 *pPacketFromGC = pFullPacket+sizeof(GCMsgHdr_t); + + EGCResults eResult = m_pSteamGameCoordinator->RetrieveMessage( &unMsgType, pPacketFromGC, m_memMsg.Count() - sizeof( GCMsgHdr_t ), &cubData ); + Assert( eResult == k_EGCResultOK ); + if( eResult == k_EGCResultOK ) + { + if( unMsgType & k_EMsgProtoBufFlag ) + { + CNetPacket *pGCPacket = CNetPacketPool::AllocNetPacket(); + pGCPacket->Init( cubData, pPacketFromGC ); + CIMsgNetPacketAutoRelease pMsgNetPacket( pGCPacket ); + + // Safety check against malformed packet + if ( pMsgNetPacket.Get() != NULL ) + { + + // dispatch the packet + GetJobMgr().BRouteMsgToJob( this, pMsgNetPacket.Get(), JobMsgInfo_t( pMsgNetPacket->GetEMsg(), pMsgNetPacket->GetSourceJobID(), pMsgNetPacket->GetTargetJobID(), k_EServerTypeGCClient ) ); + + // keep track of how much we've sent/received this message + g_theMessageList.TallySendMessage( pMsgNetPacket->GetEMsg(), cubData ); + } + + // release the packet + pGCPacket->Release(); + } + else + { + Assert( 0 == (unMsgType & k_EMsgProtoBufFlag ) ); + + // get the header so we can fix it up + GCMsgHdrEx_t *pHdr = (GCMsgHdrEx_t *)pFullPacket; + pHdr->m_eMsg = unMsgType; + pHdr->m_ulSteamID = CSteamID().ConvertToUint64(); + + // make a new packet for the message so we can dispatch it + // The CNetPacket takes ownership of the buffer allocated above + CNetPacket *pGCPacket = CNetPacketPool::AllocNetPacket(); + pGCPacket->Init( unFullSize, pFullPacket ); + CIMsgNetPacketAutoRelease pMsgNetPacket( pGCPacket ); + + // Safety check against malformed packet + if ( pMsgNetPacket.Get() != NULL ) + { + + // dispatch the packet + GetJobMgr().BRouteMsgToJob( this, pMsgNetPacket.Get(), JobMsgInfo_t( pMsgNetPacket->GetEMsg(), pMsgNetPacket->GetSourceJobID(), pMsgNetPacket->GetTargetJobID(), k_EServerTypeGCClient ) ); + + // keep track of how much we've sent/received this message + g_theMessageList.TallySendMessage( pMsgNetPacket->GetEMsg(), cubData ); + } + + // release the packet + pGCPacket->Release(); + } + } + } +} + + +//------------------------------------------------------------------------------ +// Purpose: Performs all the initialization for the GC Client instance +// Outputs: Returns false if the initialization failed +//------------------------------------------------------------------------------ +bool CGCClient::BInit( ISteamGameCoordinator *pSteamGameCoordinator ) +{ + // Set the job pool size. Threads get lazily created so if no code + // is using the thread pool, no threads will be created. + m_JobMgr.SetThreadPoolSize( GetCPUInformation()->m_nLogicalProcessors - 1 ); + + MsgRegistrationFromEnumDescriptor( EGCSystemMsg_descriptor(), GCSDK::MT_GC ); + + m_pSteamGameCoordinator = pSteamGameCoordinator; +#ifndef STEAM + m_callbackGCMessageAvailable.Register( this, &CGCClient::OnGCMessageAvailable ); +#endif + + // process any messages that are already waiting + if( m_pSteamGameCoordinator ) + { + OnGCMessageAvailable( NULL ); + } + + return true; +} + + +//------------------------------------------------------------------------------ +// Purpose: Performs all the uninitialization for the GC Client instance +//------------------------------------------------------------------------------ +void CGCClient::Uninit( ) +{ +#ifndef STEAM + m_callbackGCMessageAvailable.Unregister(); +#endif + m_pSteamGameCoordinator = NULL; + + // Clear and remove the SO caches + unsigned short nMapIndex = m_mapSOCache.FirstInorder(); + while ( m_mapSOCache.IsValidIndex( nMapIndex ) ) + { + unsigned short nNextMapIndex = m_mapSOCache.NextInorder( nMapIndex ); + + CGCClientSharedObjectCache *pSOCache = m_mapSOCache[nMapIndex]; + Assert( pSOCache ); + if ( pSOCache ) + { + // Send notifications, but only if we were actually subscribed + if ( pSOCache->BIsSubscribed() ) + { + pSOCache->NotifyUnsubscribe(); + } + + // Delete the entry + delete pSOCache; + m_mapSOCache.RemoveAt( nMapIndex ); + } + + nMapIndex = nNextMapIndex; + } +} + + +//------------------------------------------------------------------------------ +// Purpose: Finds the SO cache for this steam ID. If bCreateIfMissing is false, +// NULL will be returned if the cache can't be found +//------------------------------------------------------------------------------ +CGCClientSharedObjectCache *CGCClient::FindSOCache( const CSteamID & steamID, bool bCreateIfMissing ) +{ + CUtlMap< CSteamID, CGCClientSharedObjectCache * >::IndexType_t nCache = m_mapSOCache.Find( steamID ); + if( m_mapSOCache.IsValidIndex( nCache ) ) + return m_mapSOCache[nCache]; + else + { + if( bCreateIfMissing ) + { + Assert( steamID.IsValid() ); + if ( !steamID.IsValid() ) + return NULL; + + CGCClientSharedObjectCache *pCache = new CGCClientSharedObjectCache( steamID ); + m_mapSOCache.Insert( steamID, pCache ); + return pCache; + } + else + { + return NULL; + } + } +} + +//------------------------------------------------------------------------------ +// Purpose: Add a listener to the SO cache, creating it if necessary +//------------------------------------------------------------------------------ +void CGCClient::AddSOCacheListener( const CSteamID &ownerID, ISharedObjectListener *pListener ) +{ + Assert( ownerID.IsValid() ); + CGCClientSharedObjectCache *pCache = FindSOCache( ownerID, true ); + Assert( pCache ); + pCache->AddListener( pListener ); +} + +//------------------------------------------------------------------------------ +// Purpose: Remove listener from the SO cache, if he is listening +//------------------------------------------------------------------------------ +bool CGCClient::RemoveSOCacheListener( const CSteamID &ownerID, ISharedObjectListener *pListener ) +{ + Assert ( this != NULL ); // Damn people - check your pointers before calling! + Assert( ownerID.IsValid() ); + CGCClientSharedObjectCache *pCache = FindSOCache( ownerID, false ); + if ( pCache == NULL ) + return false; // cache doesn't exist, so we could't have ben listening + return pCache->RemoveListener( pListener ); +} + +//------------------------------------------------------------------------------ +// Purpose: Notify that the given SO cache has been unsubscribed +//------------------------------------------------------------------------------ +void CGCClient::NotifySOCacheUnsubscribed( const CSteamID & ownerID ) +{ + + CUtlMap< CSteamID, CGCClientSharedObjectCache * >::IndexType_t nCache = m_mapSOCache.Find( ownerID ); + if( m_mapSOCache.IsValidIndex( nCache ) ) + { + + CGCClientSharedObjectCache *pSOCache = m_mapSOCache[nCache]; + + // Ignore requests to remove caches that were never subscribed + if ( pSOCache->BIsSubscribed() ) + { + SOCDebug( "NotifySOCacheUnsubscribed(%s) [in cache, subscribed]\n", ownerID.Render() ); + pSOCache->NotifyUnsubscribe(); + } + else + { + SOCDebug( "NotifySOCacheUnsubscribed(%s) [in cache, not subscribed]\n", ownerID.Render() ); + } + } + else + { + SOCDebug( "NotifySOCacheUnsubscribed(%s) [not in cache]\n", ownerID.Render() ); + } +} + +//------------------------------------------------------------------------------ +// Purpose: Dump everything about everyone +//------------------------------------------------------------------------------ +void CGCClient::Dump() +{ + FOR_EACH_MAP( m_mapSOCache, idx ) + { + m_mapSOCache[ idx ]->Dump(); + } +} + +//------------------------------------------------------------------------------ +// Purpose: Finds the shared object for this steam ID and key object +//------------------------------------------------------------------------------ +CSharedObject *CGCClient::FindSharedObject( const CSteamID & ownerID, const CSharedObject & soIndex ) +{ + CGCClientSharedObjectCache *pCache = FindSOCache( ownerID, false ); + if( pCache ) + return pCache->FindSharedObject( soIndex ); + else + return NULL; +} + + +//------------------------------------------------------------------------------ +// Purpose: Validates all the statics in the GCSDKLib that need to be validated +// when linked directly into the steam servers. +//------------------------------------------------------------------------------ +#ifdef DBGFLAG_VALIDATE +void CGCClient::ValidateStatics( CValidator &validator ) +{ + // Validate the global message list + g_theMessageList.Validate( validator, "g_theMessageList" ); + + // Validate the network global memory pool + g_MemPoolMsg.Validate( validator, "g_MemPoolMsg" ); + + CNetPacketPool::ValidateGlobals( validator ); + + CJobMgr::ValidateStatics( validator, "CJobMgr" ); + CJob::ValidateStatics( validator, "CJob" ); + ValidateTempTextBuffers( validator ); + CSharedObject::ValidateStatics( validator ); + + // validate the SQL access layer + CRecordBase::ValidateStatics( validator, "CRecordBase" ); + GSchemaFull().Validate( validator, "GSchemaFull" ); + CRecordInfo::ValidateStatics( validator, "CRecordInfo" ); +} +#endif // DBGFLAG_VALIDATE + + +class CGCSOCreateJob : public CGCClientJob +{ +public: + CGCSOCreateJob( CGCClient *pClient ) : CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + CProtoBufMsg<CMsgSOSingleObject> msg( pNetPacket ); + SOCDebug( "CGCSOCreateJob(owner=%s, type=%d)\n", CSteamID( msg.Body().owner() ).Render(), msg.Body().type_id() ); + CGCClientSharedObjectCache *pSOCache = m_pGCClient->FindSOCache( msg.Body().owner() ); + if ( pSOCache ) + { + pSOCache->BCreateFromMsg( msg.Body().type_id(), msg.Body().object_data().data(), msg.Body().object_data().size() ); + Assert( msg.Body().has_version() ); + pSOCache->SetVersion( msg.Body().version() ); + } + return true; + } + +}; + +GC_REG_JOB( CGCClient, CGCSOCreateJob, "CGCSOCreateJob", k_ESOMsg_Create, GCSDK::k_EServerTypeGCClient ); + +class CGCSODestroyJob : public CGCClientJob +{ +public: + CGCSODestroyJob( CGCClient *pClient ) : CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + CProtoBufMsg<CMsgSOSingleObject> msg( pNetPacket ); + SOCDebug( "CGCSODestroyJob(owner=%s, type=%d)\n", CSteamID( msg.Body().owner() ).Render(), msg.Body().type_id() ); + CGCClientSharedObjectCache *pCache = m_pGCClient->FindSOCache( msg.Body().owner(), false ); + if( pCache ) + { + pCache->BDestroyFromMsg( msg.Body().type_id(), msg.Body().object_data().data(), msg.Body().object_data().size() ); + Assert( msg.Body().has_version() ); + pCache->SetVersion( msg.Body().version() ); + } + return true; + } + +}; + +GC_REG_JOB( CGCClient, CGCSODestroyJob, "CGCSODestroyJob", k_ESOMsg_Destroy, GCSDK::k_EServerTypeGCClient ); + +class CGCSOUpdateJob : public CGCClientJob +{ +public: + CGCSOUpdateJob( CGCClient *pClient ) : CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + CProtoBufMsg<CMsgSOSingleObject> msg( pNetPacket ); + SOCDebug( "CGCSOUpdateJob(owner=%s, type=%d)\n", CSteamID( msg.Body().owner() ).Render(), msg.Body().type_id() ); + CGCClientSharedObjectCache *pSOCache = m_pGCClient->FindSOCache( msg.Body().owner() ); + if ( pSOCache ) + { + pSOCache->BUpdateFromMsg( msg.Body().type_id(), msg.Body().object_data().data(), msg.Body().object_data().size() ); + Assert( msg.Body().has_version() ); + pSOCache->SetVersion( msg.Body().version() ); + } + return true; + } + +}; + +GC_REG_JOB( CGCClient, CGCSOUpdateJob, "CGCSOUpdateJob", k_ESOMsg_Update, GCSDK::k_EServerTypeGCClient ); + +class CGCSOUpdateMultipleJob : public CGCClientJob +{ +public: + CGCSOUpdateMultipleJob( CGCClient *pClient ) : CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + CProtoBufMsg<CMsgSOMultipleObjects> msg( pNetPacket ); + SOCDebug( "CGCSOUpdateJob(owner=%s)\n", CSteamID( msg.Body().owner() ).Render() ); + CGCClientSharedObjectCache *pSOCache = m_pGCClient->FindSOCache( msg.Body().owner() ); + if ( pSOCache ) + { + pSOCache->m_context.PreSOUpdate( eSOCacheEvent_Incremental ); + + for ( int i = 0; i < msg.Body().objects_size(); ++i ) + { + const CMsgSOMultipleObjects_SingleObject &objMessage = msg.Body().objects( i ); + SOCDebug( " type %d\n", objMessage.type_id() ); + pSOCache->BUpdateFromMsg( objMessage.type_id(), objMessage.object_data().data(), objMessage.object_data().size() ); + } + + pSOCache->m_context.PostSOUpdate( eSOCacheEvent_Incremental ); + pSOCache->SetVersion( msg.Body().version() ); + } + return true; + } + +}; + +GC_REG_JOB( CGCClient, CGCSOUpdateMultipleJob, "CGCSOUpdateMultipleJob", k_ESOMsg_UpdateMultiple, GCSDK::k_EServerTypeGCClient ); + +class CGCSOCacheSubscribedJob : public CGCClientJob +{ +public: + CGCSOCacheSubscribedJob( CGCClient *pClient ) : CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + CProtoBufMsg< CMsgSOCacheSubscribed > msg ( pNetPacket ); + CGCClientSharedObjectCache *pSOCache = m_pGCClient->FindSOCache( msg.Body().owner(), true ); + + Assert( pSOCache ); + if( pSOCache ) + { + SOCDebug( "CGCSOCacheSubscribedJob(owner=%s) [in cache]\n", CSteamID( msg.Body().owner() ).Render() ); + DbgVerify( pSOCache->BParseCacheSubscribedMsg( msg.Body() ) ); + } + else + { + SOCDebug( "CGCSOCacheSubscribedJob(owner=%s) [not in cache]\n", CSteamID( msg.Body().owner() ).Render() ); + } + + m_pGCClient->Test_CacheSubscribed( pSOCache->GetOwner() ); + + return true; + } + +}; + +GC_REG_JOB( CGCClient, CGCSOCacheSubscribedJob, "CGCSOCacheSubscribedJob", k_ESOMsg_CacheSubscribed, GCSDK::k_EServerTypeGCClient ); + +class CGCSOCacheUnsubscribedJob : public CGCClientJob +{ +public: + CGCSOCacheUnsubscribedJob( CGCClient *pClient ) : CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + CProtoBufMsg< CMsgSOCacheUnsubscribed > msg( pNetPacket ); + SOCDebug( "CGCSOCacheUnsubscribedJob(owner=%s)\n", CSteamID( msg.Body().owner() ).Render() ); + m_pGCClient->NotifySOCacheUnsubscribed( msg.Body().owner() ); + + return true; + } + +}; + +GC_REG_JOB( CGCClient, CGCSOCacheUnsubscribedJob, "CGCSOCacheUnsubscribedJob", k_ESOMsg_CacheUnsubscribed, GCSDK::k_EServerTypeGCClient ); + +class CGCSOCacheSubscriptionCheck : public CGCClientJob +{ +public: + CGCSOCacheSubscriptionCheck( CGCClient *pClient ) : CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + CProtoBufMsg< CMsgSOCacheSubscriptionCheck > msg ( pNetPacket ); + CGCClientSharedObjectCache *pSOCache = m_pGCClient->FindSOCache( msg.Body().owner(), false ); + + // if we do not have the cache or it is out-of-date, request a refresh + if ( pSOCache == NULL || !pSOCache->BIsInitialized() || pSOCache->GetVersion() != msg.Body().version() ) + { + SOCDebug( "CGCSOCacheSubscriptionCheck(owner=%s) -- need refresh\n", CSteamID( msg.Body().owner() ).Render() ); + CProtoBufMsg< CMsgSOCacheSubscriptionRefresh > msg_response( k_ESOMsg_CacheSubscriptionRefresh ); + msg_response.Body().set_owner( msg.Body().owner() ); + m_pGCClient->BSendMessage( msg_response ); + } + else + { + SOCDebug( "CGCSOCacheSubscriptionCheck(owner=%s) -- up-to-date, no refresh needed\n", CSteamID( msg.Body().owner() ).Render() ); + + // This is one method by which the GC notifies us that we are subscribed. + if ( !pSOCache->BIsSubscribed() ) + { + pSOCache->NotifyResubscribedUpToDate(); + Assert( pSOCache->BIsSubscribed() ); + } + } + return true; + } + +}; + +GC_REG_JOB( CGCClient, CGCSOCacheSubscriptionCheck, "CGCSOCacheSubscriptionCheck", k_ESOMsg_CacheSubscriptionCheck, GCSDK::k_EServerTypeGCClient ); + +} // namespace GCSDK |