//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Holds the CAccountDetails class. // //============================================================================= #include "stdafx.h" #include "accountdetails.h" #include "rtime.h" #include "gcsdk_gcmessages.pb.h" #include "memdbgon.h" // needs to be the last include in the file namespace GCSDK { GCConVar cv_account_details_cache_time( "account_details_cache_time", "600" ); GCConVar cv_account_details_failure_cache_time( "account_details_failure_cache_time", "10" ); GCConVar account_details_timeout( "account_details_timeout", "10" ); GCConVar cv_persona_name_cache_time( "persona_name_cache_time", "60" ); GCConVar cv_persona_name_failure_cache_time( "persona_name_failure_cache_time", "10" ); GCConVar cv_persona_name_batch_size( "persona_name_batch_size", "100" ); GCConVar persona_name_timeout( "persona_name_timeout", "10" ); const char *kszAccountDetailsKey = "AccountDetails-v001"; //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CAccountDetails::CAccountDetails() : m_rtimeCached( CRTime::RTime32TimeCur() ), m_bValid( false ), m_bPublicProfile( false ), m_bVacBanned( false ), m_bCyberCafe( false ), m_bSchoolAccount( false ), m_bFreeTrialAccount( false ), m_bSubscribed( false ), m_bLowViolence( false ), m_bLimited( false ), m_bAccountLocked( false ), m_bCommunityBanned( false ), m_bTradeBanned( false ), m_bIsSteamGuardEnabled( false ), m_bIsPhoneVerified( false ), m_bIsTwoFactorAuthEnabled( false ), m_bIsPhoneIdentifying( false ), m_unPackage( 0 ), m_rtimeVACBanEnd( 0 ), m_unSteamLevel( 0 ), m_unFriendCount( 0 ), m_rtimeAccountCreated( 0 ), m_rtimeTwoFactorEnabled( 0 ), m_rtimePhoneVerified( 0 ), m_unPhoneID( 0 ) { } //----------------------------------------------------------------------------- // Purpose: Initialize a fresh CAccountDetails with data from Steam //----------------------------------------------------------------------------- void CAccountDetails::Init( CGCSystemMsg_GetAccountDetails_Response &msgResponse ) { m_bValid = true; m_sAccountName = msgResponse.account_name().c_str(); m_bPublicProfile = msgResponse.is_profile_public(); m_bPublicInventory = msgResponse.is_inventory_public(); m_bVacBanned = msgResponse.is_vac_banned(); m_bCyberCafe = msgResponse.is_cyber_cafe(); m_bSchoolAccount = msgResponse.is_school_account(); m_bFreeTrialAccount = msgResponse.is_free_trial_account(); m_bSubscribed = msgResponse.is_subscribed(); m_bLowViolence = msgResponse.is_low_violence(); m_bLimited = msgResponse.is_limited(); m_bAccountLocked = msgResponse.is_account_locked_down(); m_bCommunityBanned = msgResponse.is_community_banned(); m_bTradeBanned = msgResponse.is_trade_banned(); m_unPackage = msgResponse.package(); m_rtimeVACBanEnd = msgResponse.suspension_end_time(); m_sCurrency = msgResponse.currency().c_str(); m_unSteamLevel = msgResponse.steam_level(); m_unFriendCount = msgResponse.friend_count(); m_rtimeAccountCreated = msgResponse.account_creation_time(); m_bIsSteamGuardEnabled = msgResponse.is_steamguard_enabled(); m_bIsPhoneVerified = msgResponse.is_phone_verified(); m_bIsTwoFactorAuthEnabled = msgResponse.is_two_factor_auth_enabled(); m_bIsPhoneIdentifying = msgResponse.is_phone_identifying(); m_rtimeTwoFactorEnabled = msgResponse.two_factor_enabled_time(); m_rtimePhoneVerified = msgResponse.phone_verification_time(); m_unPhoneID = msgResponse.phone_id(); } //----------------------------------------------------------------------------- // Purpose: Returns true if it's time to remove this entry from the cache //----------------------------------------------------------------------------- bool CAccountDetails::BIsExpired() const { int nCacheSeconds = BIsValid() ? cv_account_details_cache_time.GetInt() : cv_account_details_failure_cache_time.GetInt(); return m_rtimeCached + nCacheSeconds < CRTime::RTime32TimeCur(); } //----------------------------------------------------------------------------- // Purpose: Reverts this to an invalid record //----------------------------------------------------------------------------- void CAccountDetails::Reset() { m_bValid = false; m_rtimeCached = CRTime::RTime32TimeCur(); } #ifdef DBGFLAG_VALIDATE //----------------------------------------------------------------------------- // Purpose: Claims all the memory for the AccountDetails object //----------------------------------------------------------------------------- void CAccountDetails::Validate( CValidator &validator, const char *pchName ) { VALIDATE_SCOPE(); ValidateObj( m_sAccountName ); } #endif // DBGFLAG_VALIDATE //----------------------------------------------------------------------------- // Purpose: Sends a message to Steam to get a CAccountDetails object //----------------------------------------------------------------------------- class CGCJobSendGetAccountDetailsRequest : public CGCJob { CAccountDetailsManager *m_pManager; CSteamID m_SteamID; public: CGCJobSendGetAccountDetailsRequest( CGCBase *pGC, CAccountDetailsManager *pManager, const CSteamID &steamID ) : CGCJob( pGC ), m_pManager( pManager ), m_SteamID( steamID ) {} virtual bool BYieldingRunGCJob() { // Yield immediately to be sure that the calling job gets in the wakeup list BYield(); // These requests should come back very quickly, so if they don't we shouldn't wait very long // jamming up the system SetJobTimeout( account_details_timeout.GetInt() ); // Get an empty account details object CAccountDetails *pAccount = m_pManager->m_hashAccountDetailsCache.PvRecordFind( m_SteamID.GetAccountID() ); if ( NULL == pAccount ) { pAccount = m_pManager->m_hashAccountDetailsCache.PvRecordInsert( m_SteamID.GetAccountID() ); } else { // If the record isn't expired, why is it there? Assert( pAccount->BIsExpired() ); pAccount->Reset(); } CProtoBufMsg< CGCSystemMsg_GetAccountDetails > msgReqeust( k_EGCMsgGetAccountDetails ); CProtoBufMsg< CGCSystemMsg_GetAccountDetails_Response > msgReply; msgReqeust.Body().set_steamid( m_SteamID.ConvertToUint64() ); msgReqeust.Body().set_appid( GGCBase()->GetAppID() ); msgReqeust.ExpectingReply( GJobCur().GetJobID() ); // try to get the account details at most 2 times const int kMaxTries = 2; for ( int iTries = 0; iTries < kMaxTries; iTries++ ) { if( !m_pGC->BSendSystemMessage( msgReqeust ) ) { EmitWarning( SPEW_GC, 2, "Unable to send GetAccountDetails system message\n" ); continue; } // All of our request messages are identical, so if we get our replies // mixed up, it's OK. Bypass the system used to protect us against // mismatched replies. ClearFailedToReceivedMsgType( k_EGCMsgGetAccountDetailsResponse ); // Wait for the reply if( !BYieldingWaitForMsg( &msgReply, k_EGCMsgGetAccountDetailsResponse ) ) { EmitWarning( SPEW_GC, 2, "Timeout waiting for GetAccountDetails reply for SteamID %s\n", m_SteamID.Render() ); continue; } if ( k_EResultOK != msgReply.GetEResult() ) { EmitInfo( SPEW_GC, 4, 4, "GetAccountDetails request failed with result %d for SteamID %s\n", msgReply.GetEResult(), m_SteamID.Render() ); break; } Assert( msgReply.Body().eresult_deprecated() == k_EResultOK ); // Sanity check the response if ( msgReply.Body().has_accountid() && msgReply.Body().accountid() != m_SteamID.GetAccountID() ) { static bool bHasAlerted = false; if ( !bHasAlerted ) { GGCBase()->PostAlert( k_EAlertTypeInfo, true, CFmtStr( "GetAccountDetails got a response for account %d, but we were expecting a response for account %s\n", msgReply.Body().accountid(), m_SteamID.Render() ) ); bHasAlerted = true; } EmitError( SPEW_GC, "GetAccountDetails got a response for account %d, but we were expecting a response for account %s\n", msgReply.Body().accountid(), m_SteamID.Render() ); break; } // All responses should have this if ( !msgReply.Body().has_account_name() ) { EmitError( SPEW_GC, "GetAccountDetails got a response with missing fields for SteamID %s\n", m_SteamID.Render() ); break; } pAccount->Init( msgReply.Body() ); m_pManager->CachePersonaName( CSteamID( m_SteamID ), msgReply.Body().persona_name().c_str() ); // We got a response, so we shouldn't try again break; } m_pManager->WakeWaitingAccountDetailsJobs( m_SteamID ); return true; } }; //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CCachedPersonaName::CCachedPersonaName() : m_rtimeCached( 0 ) // This initialization value is important because it makes it expired on creation, // and the rest of the code will only ask Steam for a name if the cached entry is expired , m_nLoading( 0 ) , m_bPreloading( false ) { } //----------------------------------------------------------------------------- // Purpose: Destructor //----------------------------------------------------------------------------- CCachedPersonaName::~CCachedPersonaName() { Assert( !BIsLoading() ); } //----------------------------------------------------------------------------- // Purpose: Sets the newly retrieved persona name //----------------------------------------------------------------------------- void CCachedPersonaName::Init( const char *pchPersonaName ) { m_sPersonaName = pchPersonaName; m_rtimeCached = CRTime::RTime32TimeCur(); m_bPreloading = false; } //----------------------------------------------------------------------------- // Purpose: Returns true if it's time to remove this entry from the cache //----------------------------------------------------------------------------- bool CCachedPersonaName::BIsExpired() const { int nCacheSeconds = BIsValid() ? cv_persona_name_cache_time.GetInt() : cv_persona_name_failure_cache_time.GetInt(); return m_rtimeCached + nCacheSeconds < CRTime::RTime32TimeCur(); } //----------------------------------------------------------------------------- // Purpose: Returns true if there is an actual cached name //----------------------------------------------------------------------------- bool CCachedPersonaName::BIsValid() const { return !m_sPersonaName.IsEmpty(); } //----------------------------------------------------------------------------- // Purpose: Reverts this to an invalid record //----------------------------------------------------------------------------- void CCachedPersonaName::Reset() { m_sPersonaName.Clear(); m_rtimeCached = CRTime::RTime32TimeCur(); m_bPreloading = false; } //----------------------------------------------------------------------------- // Purpose: Gets the cached string //----------------------------------------------------------------------------- const char *CCachedPersonaName::GetPersonaName() const { return BIsValid() ? m_sPersonaName.Get() : "[unknown]"; } //----------------------------------------------------------------------------- // Purpose: Returns if this name is loading //----------------------------------------------------------------------------- bool CCachedPersonaName::BIsLoading() const { return m_nLoading > 0 || m_bPreloading; } //----------------------------------------------------------------------------- // Purpose: Sets that this name has been preloaded //----------------------------------------------------------------------------- void CCachedPersonaName::SetPreloading() { m_bPreloading = true; } //----------------------------------------------------------------------------- // Purpose: Sets that a job is waiting for this name to be loaded //----------------------------------------------------------------------------- void CCachedPersonaName::AddLoadingRef() { m_nLoading++; } //----------------------------------------------------------------------------- // Purpose: Releases the loading ref //----------------------------------------------------------------------------- void CCachedPersonaName::ReleaseLoadingRef() { DbgVerify( --m_nLoading >= 0 ); } #ifdef DBGFLAG_VALIDATE //----------------------------------------------------------------------------- // Purpose: Claims all the memory for the CCachedPersonaName object //----------------------------------------------------------------------------- void CCachedPersonaName::Validate( CValidator &validator, const char *pchName ) { VALIDATE_SCOPE(); ValidateObj( m_sPersonaName ); } #endif // DBGFLAG_VALIDATE //----------------------------------------------------------------------------- // Purpose: Sends a message to Steam to get a CAccountDetails object //----------------------------------------------------------------------------- class CGCJobSendGetPersonaNamesRequest : public CGCJob { CAccountDetailsManager *m_pManager; CUtlVector m_vecSteamIDs; public: CGCJobSendGetPersonaNamesRequest( CGCBase *pGC, CAccountDetailsManager *pManager, CUtlVector &vecSteamIDs ) : CGCJob( pGC ), m_pManager( pManager ) { m_vecSteamIDs.Swap( vecSteamIDs ); } virtual bool BYieldingRunGCJob() { // Yield immediately to be sure that the calling job gets in the wakeup list BYield(); // These requests should come back very quickly, so if they don't we shouldn't wait very long // jamming up the system SetJobTimeout( persona_name_timeout.GetInt() ); CProtoBufMsg< CMsgGCGetPersonaNames > msgReqeust( k_EGCMsgGetPersonaNames ); msgReqeust.ExpectingReply( GJobCur().GetJobID() ); FOR_EACH_VEC( m_vecSteamIDs, i ) { msgReqeust.Body().add_steamids( m_vecSteamIDs[i].ConvertToUint64() ); } CProtoBufMsg< CMsgGCGetPersonaNames_Response > msgReply; if( !m_pGC->BSendSystemMessage( msgReqeust ) || !BYieldingWaitForMsg( &msgReply, k_EGCMsgGetPersonaNamesResponse ) ) { FOR_EACH_VEC( m_vecSteamIDs, i ) { m_pManager->CachePersonaNameFailure( m_vecSteamIDs[i] ); } //if we are shutting down, don't bother reporting this issue, we know we won't be able to get persona names if( !GGCBase()->GetIsShuttingDown() ) { EmitWarning( SPEW_GC, 2, "GetPersonaNames request failed for %d IDs:", m_vecSteamIDs.Count() ); FOR_EACH_VEC( m_vecSteamIDs, nID ) { EmitWarning( SPEW_GC, 2, " %s", m_vecSteamIDs[ nID ].Render() ); } EmitWarning( SPEW_GC, 2, "\n" ); } } else { for ( int i = 0; i < msgReply.Body().succeeded_lookups_size(); i++ ) { const CMsgGCGetPersonaNames_Response_PersonaName &result = msgReply.Body().succeeded_lookups( i ); m_pManager->CachePersonaName( CSteamID( result.steamid() ), result.persona_name().c_str() ); } for ( int i = 0; i < msgReply.Body().failed_lookup_steamids_size(); i++ ) { m_pManager->CachePersonaNameFailure( CSteamID( msgReply.Body().failed_lookup_steamids( i ) ) ); } } FOR_EACH_VEC( m_vecSteamIDs, i ) { m_pManager->WakeWaitingPersonaNameJobs( m_vecSteamIDs[i] ); } return true; } }; //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CAccountDetailsManager::CAccountDetailsManager() : m_hashAccountDetailsCache( k_nAccountDetailsRunInterval / k_cMicroSecPerShellFrame ) , m_hashPersonaNameCache( k_nAccountDetailsRunInterval / k_cMicroSecPerShellFrame ) { m_hashAccountDetailsCache.Init( k_cAccountDetailsInit, k_cBucketAccountDetails ); m_hashPersonaNameCache.Init( k_cAccountDetailsInit, k_cBucketAccountDetails ); } //----------------------------------------------------------------------------- // Purpose: Work to be done once per frame //----------------------------------------------------------------------------- void CAccountDetailsManager::MarkFrame() { m_hashAccountDetailsCache.StartFrameSchedule( true ); m_hashPersonaNameCache.StartFrameSchedule( true ); SendBatchedPersonaNamesRequest(); } //----------------------------------------------------------------------------- // Purpose: Gets a CAccountDetails object //----------------------------------------------------------------------------- CAccountDetails *CAccountDetailsManager::YieldingGetAccountDetails( const CSteamID &steamID, bool bForceReload ) { AssertRunningJob(); if( !steamID.IsValid() || !steamID.BIndividualAccount() ) return NULL; // Check the local cache CAccountDetails *pAccountDetails = NULL; if ( BFindAccountDetailsInLocalCache( steamID, &pAccountDetails ) ) { if ( pAccountDetails && bForceReload ) { // Clear it, continue with fresh load m_hashAccountDetailsCache.Remove( pAccountDetails ); } else { return pAccountDetails; } } // Not in the local cache, ask Steam int iMapIndex = m_mapQueuedAccountDetailsRequests.Find( steamID ); if ( !m_mapQueuedAccountDetailsRequests.IsValidIndex( iMapIndex ) ) { iMapIndex = m_mapQueuedAccountDetailsRequests.Insert( steamID ); CGCJobSendGetAccountDetailsRequest *pJob = new CGCJobSendGetAccountDetailsRequest( GGCBase(), this, steamID ); pJob->StartJob( NULL ); } m_mapQueuedAccountDetailsRequests[iMapIndex].AddToTail( GJobCur().GetJobID() ); GJobCur().BYieldingWaitForWorkItem(); // Check again, if it's not there then it's not there BFindAccountDetailsInLocalCache( steamID, &pAccountDetails ); return pAccountDetails; } //----------------------------------------------------------------------------- // Purpose: Finds an AccountDetails object in the local cache. Returns true // if it was found, false if it should be checked remotely //----------------------------------------------------------------------------- bool CAccountDetailsManager::BFindAccountDetailsInLocalCache( const CSteamID &steamID, CAccountDetails **ppAccount ) { CAccountDetails *pAccountLocal = m_hashAccountDetailsCache.PvRecordFind( steamID.GetAccountID() ); if( NULL == pAccountLocal || pAccountLocal->BIsExpired() ) return false; *ppAccount = pAccountLocal->BIsValid() ? pAccountLocal : NULL; return true; } //----------------------------------------------------------------------------- // Purpose: Wakes any jobs waiting on this result //----------------------------------------------------------------------------- void CAccountDetailsManager::WakeWaitingAccountDetailsJobs( const CSteamID &steamID ) { int iMapIndex = m_mapQueuedAccountDetailsRequests.Find( steamID ); if ( !m_mapQueuedAccountDetailsRequests.IsValidIndex( iMapIndex ) ) return; CCopyableUtlVector &vecJobsWaiting = m_mapQueuedAccountDetailsRequests[iMapIndex]; FOR_EACH_VEC( vecJobsWaiting, i ) { GGCBase()->GetJobMgr().BRouteWorkItemCompletedDelayed( vecJobsWaiting[i], false ); } m_mapQueuedAccountDetailsRequests.RemoveAt( iMapIndex ); } //----------------------------------------------------------------------------- // Purpose: Gets a persona name for a user //----------------------------------------------------------------------------- const char *CAccountDetailsManager::YieldingGetPersonaName( const CSteamID &steamID ) { VPROF_BUDGET( "CAccountDetailsManager::YieldingGetPersonaName", VPROF_BUDGETGROUP_STEAM ); AssertRunningJob(); if( !steamID.IsValid() || !steamID.BIndividualAccount() ) return "[unknown]"; // Check the local cache CCachedPersonaName *pPersonaName = FindOrCreateCachedPersonaName( steamID ); if ( !pPersonaName->BIsExpired() ) return pPersonaName->GetPersonaName(); // Not in the local cache, ask Steam pPersonaName->AddLoadingRef(); // Queue the request and start a lookup job if we have enough pending int iMapIndex = m_mapQueuedPersonaNameRequests.Find( steamID ); if ( !m_mapQueuedPersonaNameRequests.IsValidIndex( iMapIndex ) ) { iMapIndex = m_mapQueuedPersonaNameRequests.Insert( steamID ); m_vecPendingPersonaNameLookups.AddToTail( steamID ); if ( m_vecPendingPersonaNameLookups.Count() >= cv_persona_name_batch_size.GetInt() ) { SendBatchedPersonaNamesRequest(); } } m_mapQueuedPersonaNameRequests[iMapIndex].AddToTail( GJobCur().GetJobID() ); GJobCur().BYieldingWaitForWorkItem(); // At this point we'll either have a persona name or we won't pPersonaName->ReleaseLoadingRef(); return pPersonaName->GetPersonaName(); } //----------------------------------------------------------------------------- // Purpose: Let's the system know that we should load the persona name for this // user, but does not block on it //----------------------------------------------------------------------------- void CAccountDetailsManager::PreloadPersonaName( const CSteamID &steamID ) { if( !steamID.IsValid() || !steamID.BIndividualAccount() ) return; // See if we already have it CCachedPersonaName *pCachedName = FindOrCreateCachedPersonaName( steamID ); if ( !pCachedName->BIsExpired() || pCachedName->BIsLoading() ) return; // Queue the request and start a lookup job if we have enough pending pCachedName->SetPreloading(); m_mapQueuedPersonaNameRequests.Insert( steamID ); m_vecPendingPersonaNameLookups.AddToTail( steamID ); if ( m_vecPendingPersonaNameLookups.Count() >= cv_persona_name_batch_size.GetInt() ) { SendBatchedPersonaNamesRequest(); } } //----------------------------------------------------------------------------- // Purpose: Sends a batch of persona name requests //----------------------------------------------------------------------------- void CAccountDetailsManager::SendBatchedPersonaNamesRequest() { if ( 0 == m_vecPendingPersonaNameLookups.Count() ) return; // Start the job. This swaps out our buffer with an empty one CGCJobSendGetPersonaNamesRequest *pJob = new CGCJobSendGetPersonaNamesRequest( GGCBase(), this, m_vecPendingPersonaNameLookups ); pJob->StartJob( NULL ); } //----------------------------------------------------------------------------- // Purpose: Caches a persona name //----------------------------------------------------------------------------- void CAccountDetailsManager::CachePersonaName( const CSteamID &steamID, const char *pchPersonaName ) { CCachedPersonaName *pCachedPersonaName = FindOrCreateCachedPersonaName( steamID ); pCachedPersonaName->Init( pchPersonaName ); } //----------------------------------------------------------------------------- // Purpose: Remembers that we failed to cache a persona name //----------------------------------------------------------------------------- void CAccountDetailsManager::CachePersonaNameFailure( const CSteamID &steamID ) { CCachedPersonaName *pCachedPersonaName = FindOrCreateCachedPersonaName( steamID ); pCachedPersonaName->Reset(); } //----------------------------------------------------------------------------- // Purpose: Purges a specific persona name from the cache //----------------------------------------------------------------------------- void CAccountDetailsManager::ClearCachedPersonaName( const CSteamID &steamID ) { CCachedPersonaName *pPersonaNameLocal = m_hashPersonaNameCache.PvRecordFind( steamID.GetAccountID() ); if( NULL != pPersonaNameLocal && !pPersonaNameLocal->BIsLoading() ) { m_hashPersonaNameCache.Remove( pPersonaNameLocal ); } } //----------------------------------------------------------------------------- // Purpose: Gets a CCachedPersonaName record //----------------------------------------------------------------------------- CCachedPersonaName *CAccountDetailsManager::FindOrCreateCachedPersonaName( const CSteamID &steamID ) { CCachedPersonaName *pPersonaName = m_hashPersonaNameCache.PvRecordFind( steamID.GetAccountID() ); if ( NULL != pPersonaName ) return pPersonaName; else return m_hashPersonaNameCache.PvRecordInsert( steamID.GetAccountID() ); } //----------------------------------------------------------------------------- // Purpose: Wakes any jobs waiting on this result //----------------------------------------------------------------------------- void CAccountDetailsManager::WakeWaitingPersonaNameJobs( const CSteamID &steamID ) { int iMapIndex = m_mapQueuedPersonaNameRequests.Find( steamID ); if ( !m_mapQueuedPersonaNameRequests.IsValidIndex( iMapIndex ) ) return; CCopyableUtlVector &vecJobsWaiting = m_mapQueuedPersonaNameRequests[iMapIndex]; FOR_EACH_VEC( vecJobsWaiting, i ) { GGCBase()->GetJobMgr().BRouteWorkItemCompletedDelayed( vecJobsWaiting[i], false ); } m_mapQueuedPersonaNameRequests.RemoveAt( iMapIndex ); } //----------------------------------------------------------------------------- // Purpose: Purges old data from the cache. Returns true if there is more // work to do //----------------------------------------------------------------------------- bool CAccountDetailsManager::BExpireRecords( CLimitTimer &limitTimer ) { VPROF_BUDGET( "Expire account details", VPROF_BUDGETGROUP_STEAM ); for ( CAccountDetails *pDetails = m_hashAccountDetailsCache.PvRecordRun(); NULL != pDetails; pDetails = m_hashAccountDetailsCache.PvRecordRun() ) { if ( pDetails->BIsExpired() ) { m_hashAccountDetailsCache.Remove( pDetails ); } if ( limitTimer.BLimitReached() ) return true; } for ( CCachedPersonaName *pPersonaName = m_hashPersonaNameCache.PvRecordRun(); NULL != pPersonaName; pPersonaName = m_hashPersonaNameCache.PvRecordRun() ) { if ( pPersonaName->BIsExpired() && !pPersonaName->BIsLoading() ) { m_hashPersonaNameCache.Remove( pPersonaName ); } if ( limitTimer.BLimitReached() ) return true; } return false; } //----------------------------------------------------------------------------- // Purpose: Prints status //----------------------------------------------------------------------------- void CAccountDetailsManager::Dump() const { int nJobsWaiting = 0; FOR_EACH_MAP_FAST( m_mapQueuedAccountDetailsRequests, iMap ) { nJobsWaiting += m_mapQueuedAccountDetailsRequests[iMap].Count(); } EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\tAccount Details: %d cached, %d lookups in flight, %d jobs waiting\n", m_hashAccountDetailsCache.Count(), m_mapQueuedAccountDetailsRequests.Count(), nJobsWaiting ); nJobsWaiting = 0; FOR_EACH_MAP_FAST( m_mapQueuedPersonaNameRequests, iMap ) { nJobsWaiting += m_mapQueuedPersonaNameRequests[iMap].Count(); } EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\tPersona Names: %d cached, %d lookups in flight, %d jobs waiting\n", m_hashPersonaNameCache.Count(), m_mapQueuedPersonaNameRequests.Count(), nJobsWaiting ); } } // namespace GCSDK