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.

586 lines
18 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Extra functionality on top of CGCClientSharedObjectCache for GCClients
  4. //
  5. //=============================================================================
  6. #include "stdafx.h"
  7. #include <time.h>
  8. #include "gcsdk/gcclient_sharedobjectcache.h"
  9. #include "gcsdk_gcmessages.pb.h"
  10. #include <typeinfo>
  11. namespace GCSDK
  12. {
  13. //#define SOCDebug(...) Msg( __VA_ARGS__ )
  14. #define SOCDebug(...) ((void)0)
  15. //----------------------------------------------------------------------------
  16. // Purpose: constructor
  17. //----------------------------------------------------------------------------
  18. CGCClientSharedObjectContext::CGCClientSharedObjectContext( const CSteamID & steamIDOwner )
  19. : m_steamIDOwner( steamIDOwner )
  20. {
  21. }
  22. //----------------------------------------------------------------------------
  23. // Purpose: Adds a new Listener to the cache. All objects in the cache will
  24. // be sent as create messages to the new Listener
  25. //----------------------------------------------------------------------------
  26. bool CGCClientSharedObjectContext::BAddListener( ISharedObjectListener *pListener )
  27. {
  28. if( m_vecListeners.HasElement( pListener ) )
  29. return false;
  30. m_vecListeners.AddToTail( pListener );
  31. return true;
  32. }
  33. //----------------------------------------------------------------------------
  34. // Purpose: Removes a Listener from the cache. All objects in the cache
  35. // will have destroy messages sent for them to the new Listener.
  36. //----------------------------------------------------------------------------
  37. bool CGCClientSharedObjectContext::BRemoveListener( ISharedObjectListener *pListener )
  38. {
  39. return m_vecListeners.FindAndRemove( pListener );
  40. }
  41. //----------------------------------------------------------------------------
  42. // Purpose: Send created/updated/destroyed calls on to all the listeners in the
  43. // context
  44. //----------------------------------------------------------------------------
  45. void CGCClientSharedObjectContext::SOCreated( const CSharedObject *pObject, ESOCacheEvent eEvent ) const
  46. {
  47. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  48. FOR_EACH_VEC( m_vecListeners, nListener )
  49. {
  50. m_vecListeners[nListener]->SOCreated( m_steamIDOwner, pObject, eEvent );
  51. }
  52. }
  53. void CGCClientSharedObjectContext::PreSOUpdate( ESOCacheEvent eEvent ) const
  54. {
  55. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  56. FOR_EACH_VEC( m_vecListeners, nListener )
  57. {
  58. m_vecListeners[nListener]->PreSOUpdate( m_steamIDOwner, eEvent );
  59. }
  60. }
  61. void CGCClientSharedObjectContext::SOUpdated( const CSharedObject *pObject, ESOCacheEvent eEvent ) const
  62. {
  63. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  64. FOR_EACH_VEC( m_vecListeners, nListener )
  65. {
  66. m_vecListeners[nListener]->SOUpdated( m_steamIDOwner, pObject, eEvent );
  67. }
  68. }
  69. void CGCClientSharedObjectContext::PostSOUpdate( ESOCacheEvent eEvent ) const
  70. {
  71. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  72. FOR_EACH_VEC( m_vecListeners, nListener )
  73. {
  74. m_vecListeners[nListener]->PostSOUpdate( m_steamIDOwner, eEvent );
  75. }
  76. }
  77. void CGCClientSharedObjectContext::SODestroyed( const CSharedObject *pObject, ESOCacheEvent eEvent ) const
  78. {
  79. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  80. FOR_EACH_VEC( m_vecListeners, nListener )
  81. {
  82. m_vecListeners[nListener]->SODestroyed( m_steamIDOwner, pObject, eEvent );
  83. }
  84. }
  85. void CGCClientSharedObjectContext::SOCacheSubscribed( const CSteamID & steamIDOwner, ESOCacheEvent eEvent ) const
  86. {
  87. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  88. FOR_EACH_VEC( m_vecListeners, nListener )
  89. {
  90. m_vecListeners[nListener]->SOCacheSubscribed( steamIDOwner, eEvent );
  91. }
  92. }
  93. void CGCClientSharedObjectContext::SOCacheUnsubscribed( const CSteamID & steamIDOwner, ESOCacheEvent eEvent ) const
  94. {
  95. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  96. FOR_EACH_VEC( m_vecListeners, nListener )
  97. {
  98. m_vecListeners[nListener]->SOCacheUnsubscribed( steamIDOwner, eEvent );
  99. }
  100. }
  101. //----------------------------------------------------------------------------
  102. // Purpose: Constructor
  103. //----------------------------------------------------------------------------
  104. CGCClientSharedObjectTypeCache::CGCClientSharedObjectTypeCache( int nTypeID, const CGCClientSharedObjectContext & context )
  105. : m_context( context ), CSharedObjectTypeCache( nTypeID )
  106. {
  107. }
  108. //----------------------------------------------------------------------------
  109. // Purpose: Destructor
  110. //----------------------------------------------------------------------------
  111. CGCClientSharedObjectTypeCache::~CGCClientSharedObjectTypeCache()
  112. {
  113. }
  114. //----------------------------------------------------------------------------
  115. // Purpose: Parses a cache subscribed message.
  116. //----------------------------------------------------------------------------
  117. bool CGCClientSharedObjectTypeCache::BParseCacheSubscribedMsg( const CMsgSOCacheSubscribed_SubscribedType & msg, CUtlVector<CSharedObject*> &vecCreatedObjects, CUtlVector<CSharedObject*> &vecUpdatedObjects, CUtlVector<CSharedObject*> &vecObjectsToDestroy )
  118. {
  119. CSharedObjectVec vecUntouchedObjects;
  120. for ( uint32 i = 0; i < GetCount(); i++ )
  121. {
  122. vecUntouchedObjects.AddToTail( GetObject( i ) );
  123. }
  124. for( uint16 usObject = 0; usObject < msg.object_data_size(); usObject++ )
  125. {
  126. bool bUpdatedExisting = false;
  127. CSharedObject *pObject = BCreateFromMsg( msg.object_data( usObject ).data(), msg.object_data( usObject ).size(), &bUpdatedExisting );
  128. if ( pObject == NULL)
  129. {
  130. Assert( pObject );
  131. return false;
  132. }
  133. // if an object was updated, remove it from the untouched list
  134. if ( bUpdatedExisting )
  135. {
  136. int index = vecUntouchedObjects.Find( pObject );
  137. if ( index != vecUntouchedObjects.InvalidIndex() )
  138. {
  139. vecUntouchedObjects[index] = NULL;
  140. }
  141. vecUpdatedObjects.AddToTail( pObject );
  142. }
  143. else
  144. {
  145. vecCreatedObjects.AddToTail( pObject );
  146. }
  147. }
  148. // all objects that weren't in the SubscribedMsg should be destroyed
  149. for ( int i = 0; i < vecUntouchedObjects.Count(); i++ )
  150. {
  151. if ( vecUntouchedObjects[i] == NULL )
  152. continue;
  153. CSharedObject *pObject = RemoveObject( *vecUntouchedObjects[i] );
  154. Assert( pObject );
  155. if( pObject )
  156. vecObjectsToDestroy.AddToTail( pObject );
  157. }
  158. return true;
  159. }
  160. void CGCClientSharedObjectTypeCache::RemoveAllObjects( CUtlVector<CSharedObject*> &vecObjects )
  161. {
  162. // Go in reverse order to avoid O(n^2) shifting the items in the array
  163. for ( int i = GetCount() - 1; i >= 0; i-- )
  164. {
  165. CSharedObject *pObject = RemoveObjectByIndex( i );
  166. Assert( pObject );
  167. if ( pObject )
  168. vecObjects.AddToTail( pObject );
  169. }
  170. }
  171. //----------------------------------------------------------------------------
  172. // Purpose: Processes a received create message for an object of this type on
  173. // the client/gameserver
  174. //----------------------------------------------------------------------------
  175. CSharedObject *CGCClientSharedObjectTypeCache::BCreateFromMsg( const void *pvData, uint32 unSize, bool *bUpdatedExisting )
  176. {
  177. CUtlBuffer bufCreate( pvData, unSize, CUtlBuffer::READ_ONLY );
  178. CSharedObject *pNewObj = CSharedObject::Create( GetTypeID() );
  179. Assert( pNewObj );
  180. if( !pNewObj )
  181. {
  182. EmitError( SPEW_SHAREDOBJ, "Unable to create object of type %d\n", GetTypeID() );
  183. return NULL;
  184. }
  185. if( !pNewObj->BParseFromMessage( bufCreate ) )
  186. {
  187. delete pNewObj;
  188. return NULL;
  189. }
  190. // Existing object?
  191. CSharedObject *pObj = FindSharedObject( *pNewObj );
  192. if( pObj )
  193. {
  194. pObj->Copy( *pNewObj );
  195. delete pNewObj;
  196. if ( bUpdatedExisting )
  197. {
  198. *bUpdatedExisting = true;
  199. }
  200. return pObj;
  201. }
  202. // New object
  203. AddObject( pNewObj );
  204. if ( bUpdatedExisting )
  205. {
  206. *bUpdatedExisting = false;
  207. }
  208. return pNewObj;
  209. }
  210. //----------------------------------------------------------------------------
  211. // Purpose: Processes a received destroy message for an object of this type on
  212. // the client/gameserver
  213. //----------------------------------------------------------------------------
  214. bool CGCClientSharedObjectTypeCache::BDestroyFromMsg( const void *pvData, uint32 unSize )
  215. {
  216. CUtlBuffer bufDestroy( pvData, unSize, CUtlBuffer::READ_ONLY );
  217. CSharedObject *pIndexObj = CSharedObject::Create( GetTypeID() );
  218. if( !pIndexObj->BParseFromMessage( bufDestroy ) )
  219. {
  220. delete pIndexObj;
  221. return false;
  222. }
  223. CSharedObject *pObject = RemoveObject( *pIndexObj );
  224. if( pObject )
  225. {
  226. m_context.SODestroyed( pObject, eSOCacheEvent_Incremental );
  227. delete pObject;
  228. }
  229. delete pIndexObj;
  230. return true;
  231. }
  232. //----------------------------------------------------------------------------
  233. // Purpose: Processes a received destroy message for an object of this type on
  234. // the client/gameserver
  235. //----------------------------------------------------------------------------
  236. bool CGCClientSharedObjectTypeCache::BUpdateFromMsg( const void *pvData, uint32 unSize )
  237. {
  238. CUtlBuffer bufUpdate( pvData, unSize, CUtlBuffer::READ_ONLY );
  239. CSharedObject *pIndexObj = CSharedObject::Create( GetTypeID() );
  240. AssertMsg1( pIndexObj, "Unable to create index object of type %d", GetTypeID() );
  241. if( !pIndexObj )
  242. return false;
  243. if( !pIndexObj->BParseFromMessage( bufUpdate ) )
  244. {
  245. delete pIndexObj;
  246. return false;
  247. }
  248. CSharedObject *pObj = FindSharedObject( *pIndexObj );
  249. bool bRet = false;
  250. if( pObj )
  251. {
  252. bufUpdate.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
  253. bRet = pObj->BUpdateFromNetwork( *pIndexObj );
  254. m_context.SOUpdated( pObj, eSOCacheEvent_Incremental );
  255. }
  256. delete pIndexObj;
  257. return bRet;
  258. }
  259. //----------------------------------------------------------------------------
  260. // Purpose: Constructor
  261. //----------------------------------------------------------------------------
  262. CGCClientSharedObjectCache::CGCClientSharedObjectCache( const CSteamID & steamIDOwner )
  263. : m_context( steamIDOwner ),
  264. m_bInitialized( false ),
  265. m_bSubscribed( false )
  266. {
  267. }
  268. //----------------------------------------------------------------------------
  269. // Purpose: Destructor
  270. //----------------------------------------------------------------------------
  271. CGCClientSharedObjectCache::~CGCClientSharedObjectCache()
  272. {
  273. }
  274. //----------------------------------------------------------------------------
  275. // Purpose: Process an incoming create message on a client/gameserver.
  276. //----------------------------------------------------------------------------
  277. bool CGCClientSharedObjectCache::BParseCacheSubscribedMsg( const CMsgSOCacheSubscribed & msg )
  278. {
  279. // Assume all type caches will be untouched
  280. CUtlVector<int> vecUntouchedTypes;
  281. for ( int i = FirstTypeCacheIndex(); i != InvalidTypeCacheIndex(); i = NextTypeCacheIndex( i ) )
  282. {
  283. CSharedObjectTypeCache *pTypeCache = GetTypeCacheByIndex( i );
  284. if ( pTypeCache )
  285. {
  286. vecUntouchedTypes.AddToTail( pTypeCache->GetTypeID() );
  287. }
  288. }
  289. // List of objects created, updated, and removed
  290. CUtlVector<CSharedObject*> vecCreatedObjects;
  291. CUtlVector<CSharedObject*> vecUpdatedObjects;
  292. CUtlVector<CSharedObject*> vecObjectsToDestroy;
  293. bool bResult = true;
  294. // Scan types in message
  295. for( uint16 usObject = 0; usObject < msg.objects_size(); usObject++ )
  296. {
  297. const CMsgSOCacheSubscribed_SubscribedType & msgType = msg.objects( usObject );
  298. // Find or create the type
  299. CGCClientSharedObjectTypeCache *pTypeCache = CreateTypeCache( msgType.type_id() );
  300. if ( pTypeCache )
  301. {
  302. int index = vecUntouchedTypes.Find( pTypeCache->GetTypeID() );
  303. if ( index != vecUntouchedTypes.InvalidIndex() )
  304. {
  305. vecUntouchedTypes[index] = -1;
  306. }
  307. }
  308. Assert( pTypeCache );
  309. if( !pTypeCache || !pTypeCache->BParseCacheSubscribedMsg( msgType, vecCreatedObjects, vecUpdatedObjects, vecObjectsToDestroy ) )
  310. bResult = false;
  311. }
  312. // any type caches that weren't in the SubscribedMsg should be cleared
  313. for ( int i = FirstTypeCacheIndex(); i != InvalidTypeCacheIndex(); i = NextTypeCacheIndex( i ) )
  314. {
  315. CGCClientSharedObjectTypeCache *pTypeCache = GetTypeCacheByIndex( i );
  316. if ( vecUntouchedTypes.Find( pTypeCache->GetTypeID() ) != vecUntouchedTypes.InvalidIndex() )
  317. {
  318. pTypeCache->RemoveAllObjects( vecObjectsToDestroy );
  319. }
  320. }
  321. // Which event is happening?
  322. ESOCacheEvent eNotificationEvent = eSOCacheEvent_Subscribed;
  323. if ( m_bSubscribed )
  324. eNotificationEvent = eSOCacheEvent_Resubscribed;
  325. // Set version, assuming we didn't have any problems. If we hit any problems,
  326. // we want to force a refresh
  327. if ( bResult )
  328. SetVersion( msg.version() );
  329. // Mark that the cache has been initialized by the server
  330. m_bInitialized = true;
  331. m_bSubscribed = true;
  332. //
  333. // Send notifications
  334. //
  335. // Initial cache subscribed
  336. m_context.SOCacheSubscribed( GetOwner(), eNotificationEvent );
  337. // Deletions
  338. for ( int i = 0 ; i < vecObjectsToDestroy.Count() ; ++i )
  339. {
  340. m_context.SODestroyed( vecObjectsToDestroy[i], eNotificationEvent );
  341. delete vecObjectsToDestroy[i];
  342. }
  343. // Updates
  344. for ( int i = 0 ; i < vecUpdatedObjects.Count() ; ++i )
  345. {
  346. m_context.SOUpdated( vecUpdatedObjects[i], eNotificationEvent );
  347. }
  348. // Created
  349. for ( int i = 0 ; i < vecCreatedObjects.Count() ; ++i )
  350. {
  351. m_context.SOUpdated( vecCreatedObjects[i], eNotificationEvent );
  352. }
  353. // Return true if everything parsed OK, or false
  354. // if we had at least one failure
  355. return bResult;
  356. }
  357. //----------------------------------------------------------------------------
  358. // Purpose: Process an incoming create message on a client/gameserver.
  359. //----------------------------------------------------------------------------
  360. void CGCClientSharedObjectCache::NotifyUnsubscribe()
  361. {
  362. if ( m_bSubscribed )
  363. {
  364. m_bSubscribed = false;
  365. m_context.SOCacheUnsubscribed( GetOwner(), eSOCacheEvent_Unsubscribed );
  366. }
  367. else
  368. {
  369. AssertMsg( m_bSubscribed, "GC Sending us Unsubscribed message when we weren't subscribed" ); // Might not be a bug, but something worth checking
  370. }
  371. }
  372. //----------------------------------------------------------------------------
  373. // Purpose: GC is telling us that the version we have is up-to-date,a nd we are subscribed
  374. //----------------------------------------------------------------------------
  375. void CGCClientSharedObjectCache::NotifyResubscribedUpToDate()
  376. {
  377. if ( !m_bSubscribed )
  378. {
  379. Assert( m_bInitialized );
  380. m_bSubscribed = true;
  381. m_context.SOCacheSubscribed( GetOwner(), eSOCacheEvent_Subscribed );
  382. }
  383. else
  384. {
  385. AssertMsg( m_bSubscribed, "Got NotifyResubscribedUpToDate when we were already subscribed?" ); // Might not be a bug, but something worth checking
  386. }
  387. }
  388. //----------------------------------------------------------------------------
  389. // Purpose: Process an incoming create message on a client/gameserver.
  390. //----------------------------------------------------------------------------
  391. bool CGCClientSharedObjectCache::BCreateFromMsg( int nTypeID, const void *pvData, uint32 unSize )
  392. {
  393. // We should be subscribed
  394. if ( !m_bInitialized || !m_bSubscribed )
  395. {
  396. // Note: We can go down and come back up without the GC knowing this.
  397. // So this can happen
  398. //Assert( m_bInitialized );
  399. //Assert( m_bSubscribed );
  400. //EmitWarning( SPEW_SHAREDOBJ, 1, "Received SOCache incremental update for cache we were not subscribed to (object type %d)\n", nTypeID );
  401. }
  402. // Locate / create the type cache
  403. CGCClientSharedObjectTypeCache *pTypeCache = CreateTypeCache( nTypeID );
  404. // Create the message or update existing
  405. bool bUpdatedExisting = false;
  406. CSharedObject *pObject = pTypeCache->BCreateFromMsg( pvData, unSize, &bUpdatedExisting );
  407. if ( pObject == NULL )
  408. return false;
  409. // Send notifications to listeners
  410. if ( bUpdatedExisting )
  411. {
  412. // This can happen --- see comment at the top of this function
  413. //Assert( !bUpdatedExisting ); // shouldn't the GC know what it's already sent us? This is weird
  414. m_context.SOUpdated( pObject, eSOCacheEvent_Incremental );
  415. }
  416. else
  417. {
  418. m_context.SOCreated( pObject, eSOCacheEvent_Incremental );
  419. }
  420. return true;
  421. }
  422. //----------------------------------------------------------------------------
  423. // Purpose: Processes an incoming destroy message on a client/gameserver.
  424. //----------------------------------------------------------------------------
  425. bool CGCClientSharedObjectCache::BDestroyFromMsg( int nTypeID, const void *pvData, uint32 unSize )
  426. {
  427. CGCClientSharedObjectTypeCache *pTypeCache = FindTypeCache( nTypeID );
  428. if( pTypeCache )
  429. {
  430. return pTypeCache->BDestroyFromMsg( pvData, unSize );
  431. }
  432. else
  433. {
  434. return false;
  435. }
  436. }
  437. //----------------------------------------------------------------------------
  438. // Purpose: Processes an incoming update message on a client/gameserver.
  439. //----------------------------------------------------------------------------
  440. bool CGCClientSharedObjectCache::BUpdateFromMsg( int nTypeID, const void *pvData, uint32 unSize )
  441. {
  442. CGCClientSharedObjectTypeCache *pTypeCache = FindTypeCache( nTypeID );
  443. if( pTypeCache )
  444. {
  445. return pTypeCache->BUpdateFromMsg( pvData, unSize );
  446. }
  447. else
  448. {
  449. return false;
  450. }
  451. }
  452. //----------------------------------------------------------------------------
  453. // Purpose: Adds a listener object to be notified of object changes in this
  454. // cache. The shared object cache does not own this object and will
  455. // not free it.
  456. //----------------------------------------------------------------------------
  457. void CGCClientSharedObjectCache::AddListener( ISharedObjectListener *pListener )
  458. {
  459. Assert( pListener );
  460. if ( !m_context.BAddListener( pListener ) )
  461. return; // was already listening, no action needed
  462. SOCDebug( "[%s] Adding listener %s\n", GetOwner().Render(), typeid( *pListener ).name() );
  463. // If we're already subscribed, then immediately send notifications
  464. if( BIsSubscribed() )
  465. {
  466. pListener->SOCacheSubscribed( GetOwner(), eSOCacheEvent_ListenerAdded );
  467. for ( int i = FirstTypeCacheIndex(); i != InvalidTypeCacheIndex(); i = NextTypeCacheIndex( i ) )
  468. {
  469. CGCClientSharedObjectTypeCache *pTypeCache = GetTypeCacheByIndex( i );
  470. for ( uint32 j = 0 ; j < pTypeCache->GetCount() ; ++j )
  471. {
  472. CSharedObject *pObject = pTypeCache->GetObject( j );
  473. pListener->SOCreated( GetOwner(), pObject, eSOCacheEvent_ListenerAdded );
  474. }
  475. }
  476. }
  477. }
  478. //----------------------------------------------------------------------------
  479. // Purpose: Removes a listener object from the list to be notified of changes
  480. // to this object cache.
  481. //----------------------------------------------------------------------------
  482. bool CGCClientSharedObjectCache::RemoveListener( ISharedObjectListener *pListener )
  483. {
  484. Assert( pListener );
  485. if ( !m_context.BRemoveListener( pListener ) )
  486. return false; // wasn't already listening, nothing to do
  487. SOCDebug( "[%s] Removing listener %s\n", GetOwner().Render(), typeid( *pListener ).name() );
  488. // If we were subscribed, then the listener's last subscribe notification
  489. // was a "you are subscribed." Send him an unsubscribed notification
  490. // so he doesn't think he's still subscribed.
  491. if( BIsSubscribed() )
  492. {
  493. pListener->SOCacheUnsubscribed( GetOwner(), eSOCacheEvent_ListenerRemoved );
  494. }
  495. return true;
  496. }
  497. } // namespace GCSDK