Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1064 lines
37 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Extra GC-specific code on top of CGCSharedObjectCache
  4. //
  5. //=============================================================================
  6. #include "stdafx.h"
  7. #include <time.h>
  8. #include "gcsdk_gcmessages.pb.h"
  9. #include "tier0/memdbgoff.h"
  10. namespace GCSDK
  11. {
  12. //----------------------------------------------------------------------------
  13. // Purpose: Container for the cached message, so we can track how many of these
  14. // things there are
  15. //----------------------------------------------------------------------------
  16. class CCachedSubscriptionMessage
  17. {
  18. DECLARE_CLASS_MEMPOOL( CCachedSubscriptionMessage );
  19. public:
  20. CCachedSubscriptionMessage()
  21. : message( k_ESOMsg_CacheSubscribed )
  22. {
  23. }
  24. ~CCachedSubscriptionMessage()
  25. {
  26. }
  27. CProtoBufMsg<CMsgSOCacheSubscribed> message;
  28. };
  29. IMPLEMENT_CLASS_MEMPOOL( CCachedSubscriptionMessage, 10 * 1000, UTLMEMORYPOOL_GROW_SLOW );
  30. }
  31. #include "tier0/memdbgon.h" // needs to be the last include in the file
  32. namespace GCSDK
  33. {
  34. //----------------------------------------------------------------------------
  35. // Purpose: Default filter object for subscriber messages whi
  36. //----------------------------------------------------------------------------
  37. class CDefaultSubscriberMessageFilter : public CISubscriberMessageFilter
  38. {
  39. public:
  40. CDefaultSubscriberMessageFilter()
  41. {
  42. }
  43. virtual bool BShouldSendAnyObjectsInCache( CGCSharedObjectTypeCache *pTypeCache, uint32 unFlags ) const
  44. {
  45. return ( CSharedObject::GetTypeFlags( pTypeCache->GetTypeID() ) & unFlags ) != 0;
  46. }
  47. virtual bool BShouldSendObject( CSharedObject *pSharedObject, uint32 unFlags ) const
  48. {
  49. return ( CSharedObject::GetTypeFlags( pSharedObject->GetTypeID() ) & unFlags ) != 0;
  50. }
  51. };
  52. //----------------------------------------------------------------------------
  53. // Purpose: constructor
  54. //----------------------------------------------------------------------------
  55. CSharedObjectContext::CSharedObjectContext( const CSteamID & steamIDOwner )
  56. : m_steamIDOwner( steamIDOwner )
  57. {
  58. }
  59. //----------------------------------------------------------------------------
  60. // Purpose: Finds the steam ID within the vector
  61. //----------------------------------------------------------------------------
  62. int CSharedObjectContext::FindSubscriber( const CSteamID& steamID ) const
  63. {
  64. FOR_EACH_VEC( m_vecSubscribers, nSubscriber )
  65. {
  66. if( steamID == m_vecSubscribers[ nSubscriber ].m_steamID )
  67. return nSubscriber;
  68. }
  69. return m_vecSubscribers.InvalidIndex();
  70. }
  71. //----------------------------------------------------------------------------
  72. // Purpose: Adds a new subscriber to the cache. All objects in the cache will
  73. // be sent as create messages to the new subscriber
  74. // Returns false if it simply incremented the refcount of the subscribers of this cache
  75. //----------------------------------------------------------------------------
  76. bool CSharedObjectContext::BAddSubscriber( const CSteamID & steamID )
  77. {
  78. MEM_ALLOC_CREDIT_CLASS();
  79. int iIdx = FindSubscriber( steamID );
  80. if ( iIdx != m_vecSubscribers.InvalidIndex() )
  81. {
  82. m_vecSubscribers[ iIdx ].m_nRefCount++;
  83. return false;
  84. }
  85. Subscriber_t Subscriber;
  86. Subscriber.m_steamID = steamID;
  87. Subscriber.m_nRefCount = 1;
  88. m_vecSubscribers.AddToTail( Subscriber );
  89. return true;
  90. }
  91. //----------------------------------------------------------------------------
  92. // Purpose: Removes a subscriber from the cache. All objects in the cache
  93. // will have destroy messages sent for them to the new subscriber.
  94. //----------------------------------------------------------------------------
  95. bool CSharedObjectContext::BRemoveSubscriber( const CSteamID & steamID )
  96. {
  97. int iIdx = FindSubscriber( steamID );
  98. if ( iIdx != m_vecSubscribers.InvalidIndex() )
  99. {
  100. m_vecSubscribers[ iIdx ].m_nRefCount--;
  101. if ( m_vecSubscribers[ iIdx ].m_nRefCount == 0 )
  102. {
  103. m_vecSubscribers.FastRemove( iIdx );
  104. return true;
  105. }
  106. }
  107. return false;
  108. }
  109. //----------------------------------------------------------------------------
  110. // Purpose: Removes all subscribers from the context
  111. //----------------------------------------------------------------------------
  112. void CSharedObjectContext::RemoveAllSubscribers()
  113. {
  114. m_vecSubscribers.RemoveAll();
  115. }
  116. //----------------------------------------------------------------------------
  117. // Purpose: Constructor
  118. //----------------------------------------------------------------------------
  119. CGCSharedObjectTypeCache::CGCSharedObjectTypeCache( int nTypeID, const CSharedObjectContext & context )
  120. : m_context( context ), CSharedObjectTypeCache( nTypeID )
  121. {
  122. }
  123. //----------------------------------------------------------------------------
  124. // Purpose: Destructor
  125. //----------------------------------------------------------------------------
  126. CGCSharedObjectTypeCache::~CGCSharedObjectTypeCache()
  127. {
  128. g_SharedObjectStats.TrackSharedObjectLifetime( GetTypeID(), -( ( int32 )GetCount() ) );
  129. }
  130. //----------------------------------------------------------------------------
  131. // Purpose: Adds a shared object of the appropriate type to this type cache.
  132. //----------------------------------------------------------------------------
  133. void CGCSharedObjectTypeCache::EnsureCapacity( uint32 nItems )
  134. {
  135. MEM_ALLOC_CREDIT_CLASS();
  136. Base::EnsureCapacity( nItems );
  137. }
  138. //----------------------------------------------------------------------------
  139. // Purpose: Adds a shared object of the appropriate type to this type cache.
  140. //----------------------------------------------------------------------------
  141. bool CGCSharedObjectTypeCache::AddObject( CSharedObject *pObject )
  142. {
  143. MEM_ALLOC_CREDIT_CLASS();
  144. Assert( GetTypeID() == pObject->GetTypeID() );
  145. if( CSharedObjectTypeCache::AddObject( pObject ) )
  146. {
  147. g_SharedObjectStats.TrackSharedObjectLifetime( GetTypeID(), 1 );
  148. return true;
  149. }
  150. return false;
  151. }
  152. //----------------------------------------------------------------------------
  153. // Purpose: Destroys the object matching the one passed in. This could be the
  154. // same one or simply one with matching index fields.
  155. //----------------------------------------------------------------------------
  156. CSharedObject *CGCSharedObjectTypeCache::RemoveObject( const CSharedObject & soIndex )
  157. {
  158. CSharedObject *pObj = CSharedObjectTypeCache::RemoveObject( soIndex );
  159. if( pObj )
  160. {
  161. g_SharedObjectStats.TrackSharedObjectLifetime( GetTypeID(), -1 );
  162. }
  163. return pObj;
  164. }
  165. //----------------------------------------------------------------------------
  166. // Purpose: Populates a message with the data to create every object of this
  167. // type in the cache.
  168. //----------------------------------------------------------------------------
  169. void CGCSharedObjectTypeCache::BuildCacheSubscribedMsg( CMsgSOCacheSubscribed_SubscribedType *pMsgType, uint32 unFlags, const CISubscriberMessageFilter &filter )
  170. {
  171. //MEM_ALLOC_CREDIT_( CFmtStr( "StartPlaying - Type BuildMsg - %s", CSharedObject::PchClassName( GetTypeID() ) ) );
  172. VPROF_BUDGET( CSharedObject::PchClassBuildCacheNodeName( GetTypeID() ), VPROF_BUDGETGROUP_STEAM );
  173. uint32 nNumSubscribed = 0;
  174. for( uint32 i=0; i<GetCount(); i++ )
  175. {
  176. CSharedObject *pObj = GetObject( i );
  177. if ( !filter.BShouldSendObject( pObj, unFlags ) )
  178. continue;
  179. if ( !pObj->BAddToMessage( pMsgType->add_object_data() ) )
  180. {
  181. EmitWarning( SPEW_SHAREDOBJ, SPEW_ALWAYS, "Failed to add create fields to message in create" );
  182. }
  183. else
  184. {
  185. nNumSubscribed++;
  186. }
  187. }
  188. //track this send
  189. g_SharedObjectStats.TrackSubscription( GetTypeID(), unFlags, nNumSubscribed );
  190. }
  191. //----------------------------------------------------------------------------
  192. // Purpose: Constructor
  193. //----------------------------------------------------------------------------
  194. CGCSharedObjectCache::CGCSharedObjectCache( const CSteamID & steamIDOwner )
  195. : m_context( steamIDOwner ),
  196. m_bInWriteback( false ),
  197. m_unWritebackTime( 0 ),
  198. m_unLRUHandle( CUtlLinkedList< CSteamID, uint32 >::InvalidIndex() ),
  199. m_unCachedSubscriptionMsgFlags( 0 ),
  200. m_pCachedSubscriptionMsg( NULL ),
  201. m_bDetectVersionChanges( true )
  202. #if WITH_SOCACHE_LRU_DEBUGGING
  203. , m_unDebugTag( 0 )
  204. #endif // WITH_SOCACHE_LRU_DEBUGGING
  205. {
  206. }
  207. //----------------------------------------------------------------------------
  208. // Purpose: Destructor
  209. //----------------------------------------------------------------------------
  210. CGCSharedObjectCache::~CGCSharedObjectCache()
  211. {
  212. delete m_pCachedSubscriptionMsg;
  213. }
  214. const CISubscriberMessageFilter &CGCSharedObjectCache::GetSubscriberMessageFilter()
  215. {
  216. static CDefaultSubscriberMessageFilter sFilter;
  217. return sFilter;
  218. }
  219. //----------------------------------------------------------------------------
  220. // Purpose: Adds a shared object to the cache.
  221. //----------------------------------------------------------------------------
  222. bool CGCSharedObjectCache::AddObject( CSharedObject *pSharedObject )
  223. {
  224. Assert( pSharedObject );
  225. if ( !GGCBase()->BIsSOCacheBeingLoaded( GetOwner() ) )
  226. {
  227. AssertMsg( GGCBase()->IsSteamIDLockedByCurJob( GetOwner() ), "Attempt to add an object to an unlocked SO cache!" );
  228. }
  229. if ( !CSharedObjectCache::AddObject( pSharedObject ) )
  230. return false;
  231. uint32 unTypeFlags = CSharedObject::GetTypeFlags( pSharedObject->GetTypeID() );
  232. if( BShouldSendToAnyClients( unTypeFlags ) )
  233. {
  234. DirtyNetworkObjectCreate( pSharedObject );
  235. }
  236. return true;
  237. }
  238. //----------------------------------------------------------------------------
  239. // Purpose: Adds a shared object to the cache without dirtying the object or the cache
  240. //----------------------------------------------------------------------------
  241. bool CGCSharedObjectCache::AddObjectClean( CSharedObject *pSharedObject )
  242. {
  243. CSharedObjectTypeCache *pTypeCache = CreateBaseTypeCache( pSharedObject->GetTypeID() );
  244. return pTypeCache->AddObjectClean( pSharedObject );
  245. }
  246. //----------------------------------------------------------------------------
  247. // Purpose: Removes the object matching the one passed in from this cache,
  248. // without destroying the actual object.
  249. //----------------------------------------------------------------------------
  250. CSharedObject *CGCSharedObjectCache::RemoveObject( const CSharedObject & soIndex )
  251. {
  252. AssertMsg( GGCBase()->IsSteamIDLockedByCurJob( GetOwner() ), "Attempt to remove an object from an unlocked SO cache!" );
  253. CSharedObject *pObject = CSharedObjectCache::RemoveObject( soIndex );
  254. if ( pObject )
  255. {
  256. uint32 unTypeFlags = CSharedObject::GetTypeFlags( pObject->GetTypeID() );
  257. if( BShouldSendToAnyClients( unTypeFlags ) )
  258. {
  259. const CISubscriberMessageFilter &filter = GetSubscriberMessageFilter();
  260. const CUtlVector< CSharedObjectContext::Subscriber_t > &vecSubscribers = m_context.GetSubscribers();
  261. CUtlVector<CSteamID> vecRecipients( 0, vecSubscribers.Count() );
  262. FOR_EACH_VEC( vecSubscribers, nSub )
  263. {
  264. const CSteamID & steamID = vecSubscribers[nSub].m_steamID;
  265. uint32 unFlags = CalcSendFlags( steamID );
  266. if( filter.BShouldSendObject( pObject, unFlags ) )
  267. {
  268. VPROF_BUDGET( CSharedObject::PchClassCreateNodeName( pObject->GetTypeID() ), VPROF_BUDGETGROUP_STEAM );
  269. vecRecipients.AddToTail( vecSubscribers[nSub].m_steamID );
  270. }
  271. }
  272. if ( vecRecipients.Count() > 0 )
  273. {
  274. VPROF_BUDGET( CSharedObject::PchClassCreateNodeName( pObject->GetTypeID() ), VPROF_BUDGETGROUP_STEAM );
  275. #if 0
  276. // Kyle says: KFIXME: this is the more current way of doing things but we're missing some multi-broadcast
  277. // code that hasn't been integrated from Dota yet.
  278. pObject->BSendDestroyToSteamIDs( vecRecipients, m_context.GetOwner(), GetVersion() );
  279. #else
  280. FOR_EACH_VEC( vecRecipients, i )
  281. {
  282. pObject->BSendDestroyToSteamID( vecRecipients[i], m_context.GetOwner(), GetVersion() );
  283. }
  284. #endif
  285. g_SharedObjectStats.TrackSharedObjectSendDestroy( pObject->GetTypeID(), vecRecipients.Count() );
  286. }
  287. }
  288. m_networkDirtyObjs.Remove( pObject );
  289. if ( 0 == m_networkDirtyObjs.Count() )
  290. {
  291. m_networkDirtyObjs.Purge();
  292. }
  293. m_networkDirtyObjsCreate.Remove( pObject );
  294. if ( 0 == m_networkDirtyObjsCreate.Count() )
  295. {
  296. m_networkDirtyObjsCreate.Purge();
  297. }
  298. m_databaseDirtyList.FindAndRemove( pObject );
  299. }
  300. return pObject;
  301. }
  302. //----------------------------------------------------------------------------
  303. // Purpose: Destroys the object matching the one passed in. This could be the
  304. // same one or simply one with matching index fields.
  305. //----------------------------------------------------------------------------
  306. bool CGCSharedObjectCache::BDestroyObject( const CSharedObject & soIndex, bool bRemoveFromDatabase )
  307. {
  308. CSharedObject *pObject = RemoveObject( soIndex );
  309. if ( !pObject )
  310. return false;
  311. if ( bRemoveFromDatabase )
  312. {
  313. pObject->BYieldingRemoveFromDatabase();
  314. }
  315. delete pObject;
  316. return true;
  317. }
  318. //----------------------------------------------------------------------------
  319. // Purpose: Adds a new subscriber to the cache. All objects in the cache will
  320. // be sent as create messages to the new subscriber
  321. //----------------------------------------------------------------------------
  322. void CGCSharedObjectCache::SetTradingPartner( const CSteamID &steamID )
  323. {
  324. if ( m_steamIDTradingPartner.IsValid() )
  325. {
  326. RemoveSubscriber( m_steamIDTradingPartner );
  327. }
  328. m_steamIDTradingPartner = steamID;
  329. if ( steamID.IsValid() )
  330. {
  331. AddSubscriber( steamID );
  332. }
  333. }
  334. //----------------------------------------------------------------------------
  335. // Purpose: Adds a new subscriber to the cache. All objects in the cache will
  336. // be sent as create messages to the new subscriber
  337. //----------------------------------------------------------------------------
  338. void CGCSharedObjectCache::AddSubscriber( const CSteamID & steamID, bool bForceSendSubscriptionMsg )
  339. {
  340. MEM_ALLOC_CREDIT_("StartPlaying - AddSubscriber" );
  341. VPROF_BUDGET( "CGCSharedObjectCache::AddSubscriber()", VPROF_BUDGETGROUP_STEAM );
  342. Assert( steamID.IsValid() );
  343. bool bAdded = m_context.BAddSubscriber( steamID );
  344. if ( bAdded || bForceSendSubscriptionMsg )
  345. {
  346. // ask the client if it really needs the contents of this cache, because we could potentially send a lot of stuff
  347. CProtoBufMsg<CMsgSOCacheSubscriptionCheck> msg( k_ESOMsg_CacheSubscriptionCheck );
  348. msg.Body().set_owner( GetOwner().ConvertToUint64() );
  349. msg.Body().set_version( GetVersion() );
  350. GGCBase()->BSendGCMsgToClient( steamID, msg );
  351. }
  352. }
  353. //----------------------------------------------------------------------------
  354. // Purpose:
  355. //----------------------------------------------------------------------------
  356. void CGCSharedObjectCache::SendSubscriberMessage( const CSteamID & steamID )
  357. {
  358. Assert( steamID.IsValid() );
  359. if ( !m_context.BIsSubscribed( steamID ) )
  360. return;
  361. uint32 unFlags = CalcSendFlags( steamID );
  362. if ( unFlags == k_ESOFlag_SendToNobody )
  363. return;
  364. // cache off the *last* one
  365. if ( unFlags != m_unCachedSubscriptionMsgFlags )
  366. {
  367. ClearCachedSubscriptionMessage();
  368. m_pCachedSubscriptionMsg = BuildSubscriberMessage( unFlags );
  369. m_unCachedSubscriptionMsgFlags = unFlags;
  370. }
  371. GGCBase()->BSendGCMsgToClient( steamID, m_pCachedSubscriptionMsg->message );
  372. }
  373. //----------------------------------------------------------------------------
  374. // Purpose:
  375. //----------------------------------------------------------------------------
  376. void CGCSharedObjectCache::ClearCachedSubscriptionMessage()
  377. {
  378. delete m_pCachedSubscriptionMsg;
  379. m_pCachedSubscriptionMsg = NULL;
  380. m_unCachedSubscriptionMsgFlags = 0;
  381. }
  382. //----------------------------------------------------------------------------
  383. // Purpose: Builds a subscriber message that can be sent to multiple clients
  384. //----------------------------------------------------------------------------
  385. CCachedSubscriptionMessage * CGCSharedObjectCache::BuildSubscriberMessage( uint32 unFlags )
  386. {
  387. MEM_ALLOC_CREDIT_( "BuildSubscriberMessage" );
  388. CCachedSubscriptionMessage *pCachedMsg = new CCachedSubscriptionMessage;
  389. CProtoBufMsg<CMsgSOCacheSubscribed> &msg = pCachedMsg->message;
  390. msg.Body().set_owner( GetOwner().ConvertToUint64() );
  391. msg.Body().set_version( GetVersion() );
  392. const CISubscriberMessageFilter &filter = GetSubscriberMessageFilter();
  393. for( int nTypeIndex = 0; nTypeIndex < GetTypeCacheCount(); nTypeIndex++ )
  394. {
  395. CGCSharedObjectTypeCache *pTypeCache = (CGCSharedObjectTypeCache *)GetTypeCacheByIndex( nTypeIndex );
  396. if( !pTypeCache || !pTypeCache->GetCount() )
  397. continue;
  398. if ( !filter.BShouldSendAnyObjectsInCache( pTypeCache, unFlags ) )
  399. continue;
  400. CMsgSOCacheSubscribed_SubscribedType *pMsgType = msg.Body().add_objects();
  401. pMsgType->set_type_id( pTypeCache->GetTypeID() );
  402. pTypeCache->BuildCacheSubscribedMsg( pMsgType, unFlags, filter );
  403. }
  404. return pCachedMsg;
  405. }
  406. //----------------------------------------------------------------------------
  407. // Purpose: Sends a message to the client to unsubscribe from this cache
  408. //----------------------------------------------------------------------------
  409. void CGCSharedObjectCache::SendUnsubscribeMessage( const CSteamID & steamID )
  410. {
  411. // Don't send these while shutting down. We'll use higher-level
  412. // connection-oriented messages instead
  413. if ( GGCBase()->GetIsShuttingDown() )
  414. return;
  415. CProtoBufMsg< CMsgSOCacheUnsubscribed > msg( k_ESOMsg_CacheUnsubscribed );
  416. msg.Body().set_owner( GetOwner().ConvertToUint64() );
  417. GGCBase()->BSendGCMsgToClient( steamID, msg );
  418. }
  419. //----------------------------------------------------------------------------
  420. // Purpose: Removes a subscriber from the cache. All objects in the cache
  421. // will have destroy messages sent for them to the new subscriber.
  422. //----------------------------------------------------------------------------
  423. void CGCSharedObjectCache::RemoveSubscriber( const CSteamID & steamID )
  424. {
  425. MEM_ALLOC_CREDIT_("CGCSharedObjectCache::RemoveSubscriber" );
  426. Assert( steamID.IsValid() );
  427. if( m_context.BRemoveSubscriber( steamID ) )
  428. {
  429. SendUnsubscribeMessage( steamID );
  430. }
  431. }
  432. //----------------------------------------------------------------------------
  433. // Purpose: Removes all subscribers from the cache.
  434. //----------------------------------------------------------------------------
  435. void CGCSharedObjectCache::RemoveAllSubscribers()
  436. {
  437. const CUtlVector< CSharedObjectContext::Subscriber_t > &vecSubscribers = m_context.GetSubscribers();
  438. FOR_EACH_VEC( vecSubscribers, i )
  439. {
  440. SendUnsubscribeMessage( vecSubscribers[i].m_steamID );
  441. }
  442. m_context.RemoveAllSubscribers();
  443. }
  444. //----------------------------------------------------------------------------
  445. // Purpose: Check to see if a given shared object is in this SO cache.
  446. //----------------------------------------------------------------------------
  447. bool CGCSharedObjectCache::IsObjectCached( const CSharedObject *pObj, uint32 nTypeID ) const
  448. {
  449. // pObj->GetTypeID() isn't called because the method is virtual,
  450. // and this code gets called from a destructor.
  451. CSharedObjectTypeCache *pTypeCache = FindBaseTypeCache( nTypeID );
  452. if ( pTypeCache == NULL )
  453. {
  454. return false;
  455. }
  456. for ( uint i = 0; i < pTypeCache->GetCount(); i++ )
  457. {
  458. if ( pTypeCache->GetObject( i ) == pObj )
  459. {
  460. return true;
  461. }
  462. }
  463. return false;
  464. }
  465. //----------------------------------------------------------------------------
  466. // Purpose: Check to see if a given shared object this SO cache's dirty lists.
  467. //----------------------------------------------------------------------------
  468. bool CGCSharedObjectCache::IsObjectDirty( const CSharedObject *pConstObj ) const
  469. {
  470. //the list types aren't really all that const correct
  471. CSharedObject* pObj = const_cast< CSharedObject* >( pConstObj );
  472. return m_databaseDirtyList.HasElement( pObj )
  473. || m_networkDirtyObjsCreate.HasElement( pObj )
  474. || m_networkDirtyObjs.HasElement( pObj );
  475. }
  476. //----------------------------------------------------------------------------
  477. // Purpose: Remembers that this field and this object are dirty
  478. //----------------------------------------------------------------------------
  479. void CGCSharedObjectCache::DirtyNetworkObject( CSharedObject *pObj )
  480. {
  481. #ifdef DEBUG
  482. // Make sure the object is in the shared object cache.
  483. Assert( IsObjectCached( pObj, pObj->GetTypeID() ) );
  484. #endif
  485. MarkDirty();
  486. // add it to our dirty list if it wasn't already dirty
  487. // if its the create queue, don't put it in the update queue
  488. UtlHashHandle_t netHandleCreate = m_networkDirtyObjsCreate.Find( pObj );
  489. if ( netHandleCreate == m_networkDirtyObjsCreate.InvalidHandle() )
  490. {
  491. m_networkDirtyObjs.Insert( pObj );
  492. }
  493. }
  494. //----------------------------------------------------------------------------
  495. // Purpose: Remembers that this field and this object are dirty
  496. //----------------------------------------------------------------------------
  497. void CGCSharedObjectCache::DirtyNetworkObjectCreate( CSharedObject *pObj )
  498. {
  499. #ifdef DEBUG
  500. // Make sure the object is in the shared object cache.
  501. Assert( IsObjectCached( pObj, pObj->GetTypeID() ) );
  502. #endif
  503. MarkDirty();
  504. // add it to our dirty list if it wasn't already dirty
  505. m_networkDirtyObjsCreate.Insert( pObj );
  506. }
  507. //----------------------------------------------------------------------------
  508. // Purpose: Remembers that this field and this object are dirty
  509. //----------------------------------------------------------------------------
  510. void CGCSharedObjectCache::DirtyDatabaseObjectField( CSharedObject *pObj, int nFieldIndex )
  511. {
  512. #ifdef DEBUG
  513. // Make sure the object is in the shared object cache.
  514. Assert( IsObjectCached( pObj, pObj->GetTypeID() ) );
  515. #endif
  516. // add it to our dirty list if it wasn't already dirty
  517. m_databaseDirtyList.DirtyObjectField( pObj, nFieldIndex );
  518. }
  519. //----------------------------------------------------------------------------
  520. // Purpose: Remembers that this field and this object are dirty
  521. //----------------------------------------------------------------------------
  522. void CGCSharedObjectCache::DirtyObjectField( CSharedObject *pObj, int nFieldIndex )
  523. {
  524. if ( pObj->BIsNetworked() )
  525. {
  526. DirtyNetworkObject( pObj );
  527. }
  528. if ( pObj->BIsDatabaseBacked() )
  529. {
  530. DirtyDatabaseObjectField( pObj, nFieldIndex );
  531. }
  532. }
  533. //----------------------------------------------------------------------------
  534. // Purpose: Sends all network updates for this object and forgets it was dirty.
  535. // If the object was not dirty, do nothing.
  536. //----------------------------------------------------------------------------
  537. void CGCSharedObjectCache::SendNetworkUpdates( CSharedObject *pObj )
  538. {
  539. UtlHashHandle_t netHandleCreate = m_networkDirtyObjsCreate.Find( pObj );
  540. if ( netHandleCreate != m_networkDirtyObjsCreate.InvalidHandle() )
  541. {
  542. SendNetworkCreateInternal( pObj );
  543. m_networkDirtyObjsCreate.Remove( pObj );
  544. }
  545. UtlHashHandle_t netHandle = m_networkDirtyObjs.Find( pObj );
  546. if ( netHandle != m_networkDirtyObjs.InvalidHandle() )
  547. {
  548. SendNetworkUpdateInternal( pObj );
  549. m_networkDirtyObjs.Remove( pObj );
  550. }
  551. }
  552. //----------------------------------------------------------------------------
  553. // Purpose: Sends network updates for all network-dirty objects
  554. //----------------------------------------------------------------------------
  555. void CGCSharedObjectCache::SendAllNetworkUpdates()
  556. {
  557. VPROF_BUDGET( "CGCSharedObjectCache::SendAllNetworkUpdates", VPROF_BUDGETGROUP_STEAM );
  558. // Create messages first
  559. if ( m_networkDirtyObjsCreate.Count() )
  560. {
  561. FOR_EACH_HASHTABLE( m_networkDirtyObjsCreate, i )
  562. {
  563. SendNetworkCreateInternal( m_networkDirtyObjsCreate.Key( i ) );
  564. }
  565. m_networkDirtyObjsCreate.RemoveAll();
  566. }
  567. // Check if anything is actually dirty first
  568. if ( !m_networkDirtyObjs.Count() )
  569. return;
  570. // build all object messages first
  571. CUtlVector< CMsgSOMultipleObjects_SingleObject > vecObjectMsgs( 0, m_networkDirtyObjs.Count() );
  572. CUtlVector< CSharedObject* > vecObjects( 0, m_networkDirtyObjs.Count() );
  573. {
  574. VPROF_BUDGET( "CGCSharedObjectCache::SendAllNetworkUpdates - build messages", VPROF_BUDGETGROUP_STEAM );
  575. FOR_EACH_HASHTABLE( m_networkDirtyObjs, i )
  576. {
  577. CSharedObject *pObj = m_networkDirtyObjs.Key( i );
  578. uint32 unFlags = CSharedObject::GetTypeFlags( pObj->GetTypeID() );
  579. if( !BShouldSendToAnyClients( unFlags ) )
  580. continue;
  581. // build single object msg
  582. vecObjects.AddToTail( pObj );
  583. int idx = vecObjectMsgs.AddToTail();
  584. CMsgSOMultipleObjects_SingleObject &msg = vecObjectMsgs[idx];
  585. msg.set_type_id( pObj->GetTypeID() );
  586. pObj->BAddToMessage( msg.mutable_object_data() );
  587. }
  588. }
  589. m_networkDirtyObjs.RemoveAll();
  590. // Check if anything is still dirty. It's possible that everything dirty are not sent to clients
  591. if ( 0 == vecObjects.Count() )
  592. return;
  593. // now build uber msg for each subscriber
  594. {
  595. VPROF_BUDGET( "CGCSharedObjectCache::SendAllNetworkUpdates - send all messages", VPROF_BUDGETGROUP_STEAM );
  596. CUtlVector<int> vecObjsToSendToThisUser( 0, vecObjects.Count() );
  597. const CUtlVector< CSharedObjectContext::Subscriber_t > &vecSubscribers = m_context.GetSubscribers();
  598. const CISubscriberMessageFilter &filter = GetSubscriberMessageFilter();
  599. FOR_EACH_VEC( vecSubscribers, nSub )
  600. {
  601. vecObjsToSendToThisUser.RemoveAll();
  602. // Figure out which, if any, objects should be sent to this user
  603. uint32 unFlags = CalcSendFlags( vecSubscribers[nSub].m_steamID );
  604. FOR_EACH_VEC( vecObjectMsgs, idx )
  605. {
  606. CSharedObject *pObj = vecObjects[idx];
  607. if( filter.BShouldSendObject( pObj, unFlags ) )
  608. {
  609. vecObjsToSendToThisUser.AddToTail( idx );
  610. }
  611. }
  612. // If we have any objects to send then build the message and send it
  613. if ( vecObjsToSendToThisUser.Count() > 0 )
  614. {
  615. VPROF_BUDGET( "CGCSharedObjectCache::SendAllNetworkUpdates - Sending Msg", VPROF_BUDGETGROUP_STEAM );
  616. CProtoBufMsg< CMsgSOMultipleObjects > msg( k_ESOMsg_UpdateMultiple );
  617. msg.Body().set_owner( GetOwner().ConvertToUint64() );
  618. msg.Body().set_version( GetVersion() );
  619. FOR_EACH_VEC( vecObjsToSendToThisUser, idx )
  620. {
  621. const CMsgSOMultipleObjects_SingleObject &objectMsg = vecObjectMsgs[vecObjsToSendToThisUser[idx]];
  622. g_SharedObjectStats.TrackSharedObjectSend( objectMsg.type_id(), 1, objectMsg.object_data().size() );
  623. // KFIXME: Tracking for when multiplex changes integrated
  624. // g_SharedObjectStats.TrackSharedObjectSend( vecObjsToSendToThisUser[idx]->type_id(), mapFlagsToObjectsToSend[iMap]->m_vecRecipients.Count(), vecObjsToSendToThisUser[idx]->object_data().size() );
  625. CMsgSOMultipleObjects_SingleObject *pObjMsg = msg.Body().add_objects();
  626. *pObjMsg = objectMsg;
  627. }
  628. GGCBase()->BSendGCMsgToClient( vecSubscribers[nSub].m_steamID, msg );
  629. }
  630. }
  631. }
  632. }
  633. //----------------------------------------------------------------------------
  634. // Purpose: Enqueues a flush instruction to Econ service for Web Inventory to update
  635. //----------------------------------------------------------------------------
  636. void CGCSharedObjectCache::FlushInventoryCache()
  637. {
  638. const CSteamID& steamID = GetOwner();
  639. if ( steamID.IsValid() && steamID.BIndividualAccount() )
  640. {
  641. GGCBase()->FlushInventoryCache( steamID.GetAccountID() );
  642. }
  643. }
  644. //----------------------------------------------------------------------------
  645. // Purpose: Adds the write to the transaction
  646. //----------------------------------------------------------------------------
  647. bool CGCSharedObjectCache::BYieldingAddWriteToTransaction( CSharedObject *pObj, CSQLAccess & sqlAccess )
  648. {
  649. CSteamID cacheOwner = m_context.GetOwner();
  650. Assert( GGCBase()->IsSteamIDLockedByCurJob( cacheOwner ) );
  651. CCopyableUtlVector< int > dirtyFields;
  652. m_databaseDirtyList.GetDirtyFieldSetByObj( pObj, dirtyFields );
  653. if ( !pObj->BYieldingAddWriteToTransaction( sqlAccess, dirtyFields ) )
  654. return false;
  655. m_databaseDirtyList.FindAndRemove( pObj );
  656. // Add a listener to the transaction to re-add these to the cache should the transaction fail. This stores off a
  657. // copy of the dirty object list, such that the caller can clear theirs now and take other actions - we'll merge
  658. // back in upon rollback.
  659. auto *pSelf = this;
  660. sqlAccess.AddRollbackListener( [pSelf, cacheOwner, pObj, dirtyFields] () \
  661. {
  662. Assert( GGCBase()->IsSteamIDLockedByCurJob( cacheOwner ) );
  663. FOR_EACH_VEC( dirtyFields, idx )
  664. {
  665. pSelf->DirtyDatabaseObjectField( pObj, dirtyFields[idx] );
  666. }
  667. // we don't need to worry about re-adding to writeback -- Callers must maintain a lock of the cache for the
  668. // duration, so we can't be processed for writeback unless the caller is writeback itself.
  669. } );
  670. return true;
  671. }
  672. //----------------------------------------------------------------------------
  673. // Purpose: Sends network updates for all network-dirty objects
  674. //----------------------------------------------------------------------------
  675. void CGCSharedObjectCache::SendNetworkCreateInternal( CSharedObject * pObj )
  676. {
  677. uint32 unFlags = CSharedObject::GetTypeFlags( pObj->GetTypeID() );
  678. if( !BShouldSendToAnyClients( unFlags ) )
  679. return;
  680. VPROF_BUDGET( CSharedObject::PchClassUpdateNodeName( pObj->GetTypeID() ), VPROF_BUDGETGROUP_STEAM );
  681. const CISubscriberMessageFilter &filter = GetSubscriberMessageFilter();
  682. const CUtlVector< CSharedObjectContext::Subscriber_t > &vecSubscribers = m_context.GetSubscribers();
  683. CUtlVector<CSteamID> vecRecipients( 0, vecSubscribers.Count() );
  684. FOR_EACH_VEC( vecSubscribers, nSub )
  685. {
  686. const CSteamID & steamID = vecSubscribers[nSub].m_steamID;
  687. Assert( steamID.IsValid() );
  688. uint32 unFlags = CalcSendFlags( steamID );
  689. if( filter.BShouldSendObject( pObj, unFlags ) )
  690. {
  691. VPROF_BUDGET( CSharedObject::PchClassCreateNodeName( pObj->GetTypeID() ), VPROF_BUDGETGROUP_STEAM );
  692. pObj->BSendCreateToSteamID( steamID, m_context.GetOwner(), GetVersion() );
  693. vecRecipients.AddToTail( steamID );
  694. }
  695. }
  696. if ( vecRecipients.Count() > 0 )
  697. {
  698. g_SharedObjectStats.TrackSharedObjectSendCreate( pObj->GetTypeID(), vecRecipients.Count() );
  699. }
  700. }
  701. //----------------------------------------------------------------------------
  702. // Purpose: Sends network updates for all network-dirty objects
  703. //----------------------------------------------------------------------------
  704. void CGCSharedObjectCache::SendNetworkUpdateInternal( CSharedObject * pObj )
  705. {
  706. uint32 unFlags = CSharedObject::GetTypeFlags( pObj->GetTypeID() );
  707. if( !BShouldSendToAnyClients( unFlags ) )
  708. return;
  709. VPROF_BUDGET( CSharedObject::PchClassUpdateNodeName( pObj->GetTypeID() ), VPROF_BUDGETGROUP_STEAM );
  710. CProtoBufMsg< CMsgSOSingleObject > msg( k_ESOMsg_Update );
  711. msg.Body().set_owner( GetOwner().ConvertToUint64() );
  712. msg.Body().set_type_id( pObj->GetTypeID() );
  713. msg.Body().set_version( GetVersion() );
  714. pObj->BAddToMessage( msg.Body().mutable_object_data() );
  715. const CISubscriberMessageFilter &filter = GetSubscriberMessageFilter();
  716. const CUtlVector< CSharedObjectContext::Subscriber_t > &vecSubscribers = m_context.GetSubscribers();
  717. FOR_EACH_VEC( vecSubscribers, nSub )
  718. {
  719. uint32 unFlags = CalcSendFlags( vecSubscribers[nSub].m_steamID );
  720. if( filter.BShouldSendObject( pObj, unFlags ) )
  721. {
  722. GGCBase()->BSendGCMsgToClient( vecSubscribers[nSub].m_steamID, msg );
  723. }
  724. }
  725. }
  726. //----------------------------------------------------------------------------
  727. // Purpose: Saves the object to the database if it is dirty.
  728. //----------------------------------------------------------------------------
  729. void CGCSharedObjectCache::YieldingWriteToDatabase( CSharedObject *pObj )
  730. {
  731. CUtlVector< int > dirtyFields;
  732. if( m_databaseDirtyList.GetDirtyFieldSetByObj( pObj, dirtyFields ) )
  733. {
  734. if ( pObj->BYieldingWriteToDatabase( dirtyFields ) )
  735. {
  736. m_databaseDirtyList.FindAndRemove( pObj );
  737. }
  738. }
  739. }
  740. //----------------------------------------------------------------------------
  741. // Purpose: Adds all dirty objects to a transaction to save to the database
  742. // Returns: The number of objects added to the transaction
  743. //----------------------------------------------------------------------------
  744. uint32 CGCSharedObjectCache::YieldingStageAllWrites( CSQLAccess & sqlAccess )
  745. {
  746. int count = 0;
  747. int nDirtyObjects = m_databaseDirtyList.NumDirtyObjects();
  748. for( int i = 0; i < nDirtyObjects; ++i )
  749. {
  750. CUtlVector< int > dirtyFields;
  751. CSharedObject *pObj;
  752. if ( m_databaseDirtyList.GetDirtyFieldSetByIndex( i, &pObj, dirtyFields ) )
  753. {
  754. if ( pObj->BYieldingAddWriteToTransaction( sqlAccess, dirtyFields ) )
  755. {
  756. count++;
  757. }
  758. }
  759. }
  760. // Add a listener to flush our list if this transaction succeeds
  761. auto *pSelf = this;
  762. sqlAccess.AddCommitListener( [pSelf, nDirtyObjects] () \
  763. {
  764. // Changing the dirty list during a transaction is not allowed
  765. Assert( nDirtyObjects == pSelf->m_databaseDirtyList.NumDirtyObjects() );
  766. pSelf->m_databaseDirtyList.RemoveAll();
  767. } );
  768. return count;
  769. }
  770. //----------------------------------------------------------------------------
  771. // Purpose: Sets the writeback flag and remembers the time it was first set
  772. // if appropriate.
  773. //----------------------------------------------------------------------------
  774. void CGCSharedObjectCache::SetInWriteback( bool bInWriteback )
  775. {
  776. if( !m_bInWriteback && bInWriteback )
  777. {
  778. m_unWritebackTime = time( NULL );
  779. }
  780. m_bInWriteback = bInWriteback;
  781. }
  782. //----------------------------------------------------------------------------
  783. // Purpose:
  784. //----------------------------------------------------------------------------
  785. void CGCSharedObjectCache::MarkDirty()
  786. {
  787. //see if we are detecting a version change when we weren't expecting one. This is often tied to loading or other events that should leave the version in a constant state
  788. //or runs the risk of creating unecessary sends
  789. AssertMsg( !m_bDetectVersionChanges, "Warning: Detected a change to the version of the SO cache when it was not expected. This is often done when an SO cache is being loaded, and can result in costly unnecessary updates of caches" );
  790. CSharedObjectCache::MarkDirty();
  791. ClearCachedSubscriptionMessage();
  792. m_ulVersion = GGCHost()->GenerateGID();
  793. GGCBase()->AddCacheToVersionChangedList( this );
  794. }
  795. //----------------------------------------------------------------------------
  796. // Purpose: Determine the send flags for the given steam id
  797. //----------------------------------------------------------------------------
  798. uint32 CGCSharedObjectCache::CalcSendFlags( const CSteamID & steamID ) const
  799. {
  800. if( m_context.GetOwner() == steamID )
  801. return k_ESOFlag_SendToOwner;
  802. if( steamID.BIndividualAccount() )
  803. {
  804. return k_ESOFlag_SendToOtherUsers;
  805. }
  806. else if( steamID.BGameServerAccount() )
  807. {
  808. return k_ESOFlag_SendToOtherGameservers | k_ESOFlag_SendToQuestObjectiveTrackers;
  809. }
  810. // What sort of an account is this?!
  811. Assert( steamID.IsValid() );
  812. return k_ESOFlag_SendToNobody;
  813. }
  814. //----------------------------------------------------------------------------
  815. // Purpose: Given the flags, are we supposed to send anything to them
  816. //----------------------------------------------------------------------------
  817. bool CGCSharedObjectCache::BShouldSendToAnyClients( uint32 unFlags ) const
  818. {
  819. return 0 != (unFlags & (k_ESOFlag_SendToOwner | k_ESOFlag_SendToOtherGameservers | k_ESOFlag_SendToOtherUsers) );
  820. }
  821. //----------------------------------------------------------------------------
  822. // Purpose: Dumps all the objects in the type cache
  823. //----------------------------------------------------------------------------
  824. void CGCSharedObjectCache::Dump() const
  825. {
  826. CSharedObjectCache::Dump();
  827. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\t Subscribers (%d). Version (%llu):\n", m_context.GetSubscribers().Count(), m_ulVersion );
  828. FOR_EACH_VEC( m_context.GetSubscribers(), nSub )
  829. {
  830. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\t\t%s\n", m_context.GetSubscribers()[nSub].m_steamID.Render() );
  831. }
  832. }
  833. //----------------------------------------------------------------------------
  834. // Purpose:
  835. //----------------------------------------------------------------------------
  836. void CGCSharedObjectCache::DumpDirtyObjects() const
  837. {
  838. if ( m_networkDirtyObjsCreate.Count() > 0 )
  839. {
  840. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\t Num network create dirty (%d):\n", m_networkDirtyObjsCreate.Count() );
  841. FOR_EACH_HASHTABLE( m_networkDirtyObjsCreate, i )
  842. {
  843. CSharedObject *pObj = m_networkDirtyObjsCreate.Key(i);
  844. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\t\t Object type id: %d\n", pObj->GetTypeID() );
  845. }
  846. }
  847. if ( m_networkDirtyObjs.Count() > 0 )
  848. {
  849. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\t Num network dirty (%d):\n", m_networkDirtyObjs.Count() );
  850. FOR_EACH_HASHTABLE( m_networkDirtyObjs, i )
  851. {
  852. CSharedObject *pObj = m_networkDirtyObjs.Key(i);
  853. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\t\t Object type id: %d\n", pObj->GetTypeID() );
  854. }
  855. }
  856. if ( m_databaseDirtyList.NumDirtyObjects() > 0 )
  857. {
  858. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\t Num database dirty (%d):\n", m_databaseDirtyList.NumDirtyObjects() );
  859. for( int i = 0; i < m_databaseDirtyList.NumDirtyObjects(); ++i )
  860. {
  861. CSharedObject *pObj = NULL;
  862. CUtlVector<int> fieldSet;
  863. m_databaseDirtyList.GetDirtyFieldSetByIndex( i, &pObj, fieldSet );
  864. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\t\t Object type id: %d\n", pObj->GetTypeID() );
  865. }
  866. }
  867. }
  868. //----------------------------------------------------------------------------
  869. // Purpose: Claims all the memory for the cache
  870. //----------------------------------------------------------------------------
  871. #ifdef DBGFLAG_VALIDATE
  872. void CGCSharedObjectCache::Validate( CValidator &validator, const char *pchName )
  873. {
  874. VALIDATE_SCOPE();
  875. CGCSharedObjectCache::Validate( validator, pchName );
  876. ValidateObj( m_networkDirtyObjs );
  877. ValidateObj( m_networkDirtyObjsCreate );
  878. ValidateObj( m_databaseDirtyList );
  879. }
  880. #endif
  881. //-----------------------------------------------------------------------------
  882. // Purpose: Interface for profiling SO cache stats
  883. //-----------------------------------------------------------------------------
  884. GC_CON_COMMAND( so_profile_on, "Starts the profiling of shared object stats" )
  885. {
  886. //stop any previous one to make sure that our time is up to date
  887. g_SharedObjectStats.StopCollectingStats();
  888. g_SharedObjectStats.ResetStats();
  889. g_SharedObjectStats.StartCollectingStats();
  890. }
  891. GC_CON_COMMAND( so_profile_off, "Stops the profiling of shared object stats" )
  892. {
  893. g_SharedObjectStats.StopCollectingStats();
  894. }
  895. GC_CON_COMMAND( so_profile_dump, "Displays the stats collected from profiling shared object stats" )
  896. {
  897. g_SharedObjectStats.ReportCollectedStats();
  898. }
  899. } // namespace GCSDK