mirror of
https://github.com/nillerusr/source-engine.git
synced 2026-08-10 02:39:35 +00:00
1
This commit is contained in:
@@ -0,0 +1,721 @@
|
||||
//========= 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<CSteamID> m_vecSteamIDs;
|
||||
|
||||
public:
|
||||
CGCJobSendGetPersonaNamesRequest( CGCBase *pGC, CAccountDetailsManager *pManager, CUtlVector<CSteamID> &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<JobID_t> &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<JobID_t> &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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,120 @@
|
||||
//========= Copyright ©, Valve LLC, All rights reserved. ======================
|
||||
//
|
||||
// Purpose: Defines a buffer pool used to group small allocations
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "bufferpool.h"
|
||||
|
||||
using namespace GCSDK;
|
||||
|
||||
CUtlVector<CBufferPool *> CBufferPool::sm_vecBufferPools;
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//----------------------------------------------------------------------------
|
||||
CBufferPool::CBufferPool( const char *pchName, const GCConVar &cvMaxSizeMB, const GCConVar &cvInitBufferSize, int nFlags )
|
||||
: m_nBuffersInUse( 0 )
|
||||
, m_nBuffersTotal( 0 )
|
||||
, m_nHighWatermark( 0 )
|
||||
, m_cubFree( 0 )
|
||||
, m_sName( pchName )
|
||||
, m_cvMaxSizeMB( cvMaxSizeMB )
|
||||
, m_cvInitBufferSize( cvInitBufferSize )
|
||||
, m_nFlags( nFlags )
|
||||
{
|
||||
sm_vecBufferPools.AddToTail( this );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//----------------------------------------------------------------------------
|
||||
CBufferPool::~CBufferPool()
|
||||
{
|
||||
m_vecFreeBuffers.PurgeAndDeleteElements();
|
||||
sm_vecBufferPools.FindAndRemove( this );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Gives a bind param buffer to a query
|
||||
//----------------------------------------------------------------------------
|
||||
CUtlBuffer *CBufferPool::GetBuffer()
|
||||
{
|
||||
m_nBuffersInUse++;
|
||||
|
||||
if ( m_vecFreeBuffers.Count() > 0 )
|
||||
{
|
||||
CUtlBuffer *pBuffer = m_vecFreeBuffers.Tail();
|
||||
m_vecFreeBuffers.Remove( m_vecFreeBuffers.Count() - 1 );
|
||||
m_cubFree -= pBuffer->Size();
|
||||
|
||||
return pBuffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_nBuffersTotal++;
|
||||
m_nHighWatermark = max( m_nBuffersTotal, m_nHighWatermark );
|
||||
return new CUtlBuffer( 0, m_cvInitBufferSize.GetInt(), m_nFlags );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Returns a bind param buffer when a query is done with it
|
||||
//----------------------------------------------------------------------------
|
||||
void CBufferPool::ReturnBuffer( CUtlBuffer *pBuffer )
|
||||
{
|
||||
m_nBuffersInUse--;
|
||||
|
||||
if ( ( int64 )( m_cubFree + pBuffer->Size() ) <= ( m_cvMaxSizeMB.GetInt() * k_nMegabyte ) )
|
||||
{
|
||||
pBuffer->Clear();
|
||||
m_cubFree += pBuffer->Size();
|
||||
m_vecFreeBuffers.AddToTail( pBuffer );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_nBuffersTotal--;
|
||||
delete pBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Print statistics to the console
|
||||
//----------------------------------------------------------------------------
|
||||
void CBufferPool::DumpPools()
|
||||
{
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Reusable Buffer Pools:\n" );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS,
|
||||
" Pool Buffers Free Max Buffers Total Mem (est) Free Mem\n" );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS,
|
||||
" ------------------------------ ------------ ------------ ------------ ---------------- ----------------\n" );
|
||||
|
||||
FOR_EACH_VEC( sm_vecBufferPools, i )
|
||||
{
|
||||
CBufferPool *pPool = sm_vecBufferPools[i];
|
||||
|
||||
int nTotalEst = 0;
|
||||
if ( pPool->m_vecFreeBuffers.Count() > 0 )
|
||||
{
|
||||
nTotalEst = pPool->m_cubFree / pPool->m_vecFreeBuffers.Count() * pPool->m_nBuffersTotal;
|
||||
}
|
||||
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " %-30s %12d %12d %12d %16s %16s\n",
|
||||
pPool->m_sName.Get(),
|
||||
pPool->m_nBuffersTotal,
|
||||
pPool->m_vecFreeBuffers.Count(),
|
||||
pPool->m_nHighWatermark,
|
||||
Q_pretifymem( nTotalEst, 0, true ),
|
||||
Q_pretifymem( pPool->m_cubFree, 0, true )
|
||||
);
|
||||
}
|
||||
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\n" );
|
||||
}
|
||||
@@ -0,0 +1,526 @@
|
||||
//====== Copyright ©, Valve Corporation, All rights reserved. =================
|
||||
//
|
||||
// Purpose: Defines a directory for all the GC processes for an app
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "gcsdk/directory.h"
|
||||
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CGCDirTypeInstance::CGCDirTypeInstance( const char* pszTypeName, uint32 nType, uint32 nInstance, const KeyValues *pKVConfig, const CGCDirProcess* pProcess )
|
||||
: m_nType( nType )
|
||||
, m_sTypeName( pszTypeName )
|
||||
, m_nInstance( nInstance )
|
||||
, m_pProcess( pProcess )
|
||||
{
|
||||
m_pConfig = pKVConfig->MakeCopy();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CGCDirTypeInstance::~CGCDirTypeInstance()
|
||||
{
|
||||
m_pConfig->deleteThis();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns the type for this GC
|
||||
//-----------------------------------------------------------------------------
|
||||
uint32 CGCDirTypeInstance::GetType() const
|
||||
{
|
||||
return m_nType;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
const char* CGCDirTypeInstance::GetTypeName() const
|
||||
{
|
||||
return m_sTypeName;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns the instance index for GCs of this type
|
||||
//-----------------------------------------------------------------------------
|
||||
uint32 CGCDirTypeInstance::GetInstance() const
|
||||
{
|
||||
return m_nInstance;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets any additional configuration data for this GC
|
||||
//-----------------------------------------------------------------------------
|
||||
KeyValues * CGCDirTypeInstance::GetConfig() const
|
||||
{
|
||||
return m_pConfig;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets the box that this GC was associated with
|
||||
//-----------------------------------------------------------------------------
|
||||
const CGCDirProcess* CGCDirTypeInstance::GetProcess( ) const
|
||||
{
|
||||
return m_pProcess;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CGCDirProcess::CGCDirProcess( uint32 nGCDirIndex, const char *pchName, const char* pchProcessType, const KeyValues *pKVConfig ) :
|
||||
m_iDirGC( nGCDirIndex ),
|
||||
m_sName( pchName ),
|
||||
m_sType( pchProcessType ),
|
||||
m_nTypeMask( 0 )
|
||||
{
|
||||
m_pConfig = pKVConfig->MakeCopy();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CGCDirProcess::~CGCDirProcess()
|
||||
{
|
||||
// Don't need to delete our pointers. They were allocated by the directory
|
||||
// and will be cleaned up by the directory.
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets any additional configuration data for this GC
|
||||
//-----------------------------------------------------------------------------
|
||||
KeyValues * CGCDirProcess::GetConfig() const
|
||||
{
|
||||
return m_pConfig;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets the name for this box
|
||||
//-----------------------------------------------------------------------------
|
||||
const char *CGCDirProcess::GetName() const
|
||||
{
|
||||
return m_sName.Get();
|
||||
}
|
||||
|
||||
const char *CGCDirProcess::GetProcessType() const
|
||||
{
|
||||
return m_sType.Get();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets the number of GCs assigned to this box
|
||||
//-----------------------------------------------------------------------------
|
||||
uint32 CGCDirProcess::GetTypeInstanceCount() const
|
||||
{
|
||||
return m_vecGCs.Count();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets the specified GC definition
|
||||
//-----------------------------------------------------------------------------
|
||||
const CGCDirTypeInstance *CGCDirProcess::GetTypeInstance( uint32 nGCIndex ) const
|
||||
{
|
||||
bool bValidIndex = m_vecGCs.IsValidIndex( nGCIndex );
|
||||
Assert( bValidIndex );
|
||||
if ( !bValidIndex )
|
||||
return NULL;
|
||||
|
||||
return m_vecGCs[ nGCIndex ];
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Adds a GC to this box
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGCDirProcess::AddGC( CGCDirTypeInstance *pGC )
|
||||
{
|
||||
if ( !pGC )
|
||||
{
|
||||
Assert( false );
|
||||
return;
|
||||
}
|
||||
|
||||
//set this within our type mask
|
||||
Assert( pGC->GetType() < 64 );
|
||||
m_nTypeMask |= ( uint64 )1 << pGC->GetType();
|
||||
|
||||
m_vecGCs.AddToTail( pGC );
|
||||
if( !m_vecUniqueTypeList.HasElement( pGC->GetType() ) )
|
||||
{
|
||||
m_vecUniqueTypeList.Insert( pGC->GetType() );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
uint32 CGCDirProcess::GetGCDirIndex() const
|
||||
{
|
||||
return m_iDirGC;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CGCDirProcess::HasInstanceOfType( uint32 nType ) const
|
||||
{
|
||||
return ( m_nTypeMask & ( ( uint64 )1 << nType ) ) != 0;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
const CUtlSortVector< uint32 >& CGCDirProcess::GetUniqueTypeList() const
|
||||
{
|
||||
return m_vecUniqueTypeList;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CDirectory::CDirectory()
|
||||
: m_bInitialized( false )
|
||||
, m_mapGCsByType( DefLessFunc( int32 ) )
|
||||
, m_mapRegisteredGCTypes( DefLessFunc( int32 ) )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CDirectory::~CDirectory()
|
||||
{
|
||||
m_vecProcesses.PurgeAndDeleteElements();
|
||||
m_vecTypeInstances.PurgeAndDeleteElements();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Initializes the directory
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CDirectory::BInit( KeyValues *pKVDirectory )
|
||||
{
|
||||
Assert( !m_bInitialized );
|
||||
if ( m_bInitialized )
|
||||
return false;
|
||||
|
||||
if ( NULL == pKVDirectory )
|
||||
{
|
||||
AssertMsg( false, "Null KV passed to CDirectory::BInit()" );
|
||||
return false;
|
||||
}
|
||||
|
||||
CUtlSymbolTable processNamesTable( 0, 16, true );
|
||||
FOR_EACH_TRUE_SUBKEY( pKVDirectory, pkvProcess )
|
||||
{
|
||||
if( 0 != Q_stricmp( pkvProcess->GetName( ), "process" ) )
|
||||
continue;
|
||||
|
||||
//get the name of this process and ensure that it is unique
|
||||
const char *pchProcessName = pkvProcess->GetString( "name" );
|
||||
if( !pchProcessName || !pchProcessName[ 0 ] )
|
||||
{
|
||||
AssertMsg( false, "Process defined in the config with no name" );
|
||||
return false;
|
||||
}
|
||||
if( processNamesTable.Find( pchProcessName ).IsValid() )
|
||||
{
|
||||
AssertMsg( false, "Duplicate box \"%s\" encountered while parsing the config", pchProcessName );
|
||||
return false;
|
||||
}
|
||||
processNamesTable.AddString( pchProcessName );
|
||||
|
||||
//get the type associated with this process
|
||||
const char *pchProcessType = pkvProcess->GetString( "type" );
|
||||
if( !pchProcessType || !pchProcessType[ 0 ] )
|
||||
{
|
||||
AssertMsg( false, "Process %s defined in the config with no process type associated with it", pchProcessName );
|
||||
return false;
|
||||
}
|
||||
|
||||
//create our new process info
|
||||
CGCDirProcess *pProcess = new CGCDirProcess( m_vecProcesses.Count( ), pchProcessName, pchProcessType, pkvProcess );
|
||||
m_vecProcesses.AddToTail( pProcess );
|
||||
|
||||
FOR_EACH_SUBKEY( pkvProcess, pkvType )
|
||||
{
|
||||
if( 0 != Q_stricmp( pkvType->GetName(), "gc" ) )
|
||||
continue;
|
||||
|
||||
const char *pchGCType = NULL;
|
||||
if( pkvType->GetFirstSubKey() )
|
||||
{
|
||||
pchGCType = pkvType->GetString( "gc" );
|
||||
}
|
||||
else
|
||||
{
|
||||
pchGCType = pkvType->GetString( );
|
||||
}
|
||||
|
||||
if ( !pchGCType || !pchGCType[0] )
|
||||
{
|
||||
AssertMsg( false, "GC defined on box \"%s\" in the config with no type", pchProcessName );
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 nGCType = GetGCTypeForName( pchGCType );
|
||||
if ( s_nInvalidGCType == nGCType )
|
||||
{
|
||||
AssertMsg( false, "GC defined on box \"%s\" with unknown type \"%s\" encountered while parsing the config", pchProcessName, pchGCType );
|
||||
return false;
|
||||
}
|
||||
|
||||
//note that to get the count we can't call GetGCCountForType until after we register since otherwise we don't have a type entry
|
||||
int nGCTypeIndex = m_mapGCsByType.Find( nGCType );
|
||||
if ( !m_mapGCsByType.IsValidIndex( nGCType ) )
|
||||
{
|
||||
nGCTypeIndex = m_mapGCsByType.Insert( nGCType );
|
||||
}
|
||||
|
||||
CGCDirTypeInstance *pGC = new CGCDirTypeInstance( pchGCType, nGCType, m_mapGCsByType[ nGCTypeIndex ].Count(), pkvType, pProcess );
|
||||
pProcess->AddGC( pGC );
|
||||
m_vecTypeInstances.AddToTail( pGC );
|
||||
m_mapGCsByType[ nGCTypeIndex ].AddToTail( pGC );
|
||||
}
|
||||
}
|
||||
|
||||
// There must be exactly one master GC defined. Make sure it exists
|
||||
int32 nMasterType = GetGCTypeForName( "master" );
|
||||
if ( s_nInvalidGCType == nMasterType )
|
||||
{
|
||||
AssertMsg( false, "Master GC type is not registered" );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 1 != GetGCCountForType( nMasterType ) )
|
||||
{
|
||||
AssertMsg( false, "Incorrect number of master GCs in the config. Expected: 1 Actual: %d", GetGCCountForType( nMasterType ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
const CGCDirTypeInstance *pGC = GetGCInstanceForType( nMasterType, 0 );
|
||||
if ( 0 != pGC->GetProcess()->GetGCDirIndex() )
|
||||
{
|
||||
AssertMsg( false, "The master GC must be contained within the first GC defined." );
|
||||
return false;
|
||||
}
|
||||
|
||||
m_bInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Registers a type of GC
|
||||
//-----------------------------------------------------------------------------
|
||||
void CDirectory::RegisterGCType( int32 nTypeID, const char *pchName )
|
||||
{
|
||||
Assert( s_nInvalidGCType != nTypeID );
|
||||
if ( s_nInvalidGCType == nTypeID )
|
||||
return;
|
||||
|
||||
bool bHasElement = m_mapRegisteredGCTypes.IsValidIndex( m_mapRegisteredGCTypes.Find( nTypeID ) );
|
||||
AssertMsg( !bHasElement, "A GC of type %d has already been registered", nTypeID );
|
||||
if ( bHasElement )
|
||||
return;
|
||||
|
||||
bHasElement = m_dictRegisteredGCNameToType.HasElement( pchName );
|
||||
AssertMsg( !bHasElement, "A GC type with name \"%s\" has already been registered", pchName );
|
||||
if ( bHasElement )
|
||||
return;
|
||||
|
||||
RegisteredGCType_t &gcReg = m_mapRegisteredGCTypes[ m_mapRegisteredGCTypes.Insert( nTypeID ) ];
|
||||
gcReg.m_strName = pchName;
|
||||
|
||||
m_dictRegisteredGCNameToType.Insert( pchName, nTypeID );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets the number of boxes hosting GCs in the universe
|
||||
//-----------------------------------------------------------------------------
|
||||
uint32 CDirectory::GetProcessCount() const
|
||||
{
|
||||
return m_vecProcesses.Count();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets the definition for a particular box
|
||||
//-----------------------------------------------------------------------------
|
||||
const CGCDirProcess *CDirectory::GetProcess( int32 nIndex ) const
|
||||
{
|
||||
if( !m_vecProcesses.IsValidIndex( nIndex ) )
|
||||
{
|
||||
Assert( false );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return m_vecProcesses[ nIndex ];
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets the number of GCs in the universe
|
||||
//-----------------------------------------------------------------------------
|
||||
uint32 CDirectory::GetGCTypeInstanceCount() const
|
||||
{
|
||||
return m_vecTypeInstances.Count();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
const CGCDirTypeInstance *CDirectory::GetGCTypeInstance( uint32 iDirGC ) const
|
||||
{
|
||||
if( iDirGC >= ( uint32 )m_vecTypeInstances.Count() )
|
||||
{
|
||||
Assert( false );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return m_vecTypeInstances[ iDirGC ];
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets the number of GCs in the universe of the given type
|
||||
//-----------------------------------------------------------------------------
|
||||
int32 CDirectory::GetGCCountForType( int32 nTypeID ) const
|
||||
{
|
||||
int nIndex = m_mapGCsByType.Find( nTypeID );
|
||||
if ( !m_mapGCsByType.IsValidIndex( nIndex ) )
|
||||
{
|
||||
EmitWarning( SPEW_GC, 2, "CDirectory::GetGCCountForType() called with unregistered type %d (%s)\n", nTypeID, GetNameForGCType( nTypeID ) );
|
||||
return 0;
|
||||
}
|
||||
|
||||
return m_mapGCsByType[nIndex].Count();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
const CGCDirTypeInstance *CDirectory::GetGCInstanceForType( int32 nTypeID, int32 nInstance ) const
|
||||
{
|
||||
int nIndex = m_mapGCsByType.Find( nTypeID );
|
||||
if ( !m_mapGCsByType.IsValidIndex( nIndex ) )
|
||||
{
|
||||
EmitWarning( SPEW_GC, 2, "CDirectory::GetGCInstanceForType() called with unregistered type %d (%s)\n", nTypeID, GetNameForGCType( nTypeID ) );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const CCopyableUtlVector<CGCDirTypeInstance *> &vecGCs = m_mapGCsByType[ nIndex ];
|
||||
bool bValidIndex = vecGCs.IsValidIndex( nInstance );
|
||||
Assert( bValidIndex );
|
||||
if ( !bValidIndex )
|
||||
return NULL;
|
||||
|
||||
return vecGCs[ nInstance ];
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Given a type and index, this will hash it to be a valid in-range index for that type and return the directory index
|
||||
//-----------------------------------------------------------------------------
|
||||
int32 CDirectory::GetGCDirIndexForInstance( int32 nTypeID, uint32 nInstanceIndex ) const
|
||||
{
|
||||
int nIndex = m_mapGCsByType.Find( nTypeID );
|
||||
if ( !m_mapGCsByType.IsValidIndex( nIndex ) )
|
||||
{
|
||||
EmitWarning( SPEW_GC, 2, "CDirectory::GetGCDirIndexForInstance() called with unregistered type %d (%s)\n", nTypeID, GetNameForGCType( nTypeID ) );
|
||||
return -1;
|
||||
}
|
||||
|
||||
const CCopyableUtlVector<CGCDirTypeInstance *> &vecGCs = m_mapGCsByType[ nIndex ];
|
||||
uint32 nWrappedIndex = nInstanceIndex % vecGCs.Count();
|
||||
return vecGCs[ nWrappedIndex ]->GetProcess()->GetGCDirIndex();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: given a GC type, this will add all of the GC indices associated with that type onto the provided vector
|
||||
//-----------------------------------------------------------------------------
|
||||
void CDirectory::GetGCDirIndicesForType( int32 nTypeID, CUtlVector< uint32 >& vecIndices ) const
|
||||
{
|
||||
int nIndex = m_mapGCsByType.Find( nTypeID );
|
||||
if ( m_mapGCsByType.IsValidIndex( nIndex ) )
|
||||
{
|
||||
const CCopyableUtlVector<CGCDirTypeInstance *> &vecGCs = m_mapGCsByType[ nIndex ];
|
||||
FOR_EACH_VEC( vecGCs, nGC )
|
||||
{
|
||||
vecIndices.AddToTail( vecGCs[ nGC ]->GetProcess()->GetGCDirIndex() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets a name for a registered GC type
|
||||
//-----------------------------------------------------------------------------
|
||||
const char *CDirectory::GetNameForGCType( int32 nTypeID ) const
|
||||
{
|
||||
int nIndex = m_mapRegisteredGCTypes.Find( nTypeID );
|
||||
if ( !m_mapRegisteredGCTypes.IsValidIndex( nIndex ) )
|
||||
return "unknown";
|
||||
|
||||
return m_mapRegisteredGCTypes[nIndex].m_strName.Get();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets the creation factory for a registered GC types
|
||||
//-----------------------------------------------------------------------------
|
||||
CDirectory::GCFactory_t CDirectory::GetFactoryForProcessType( const char* pszProcessType ) const
|
||||
{
|
||||
int nIndex = m_dictProcessTypeToFactory.Find( pszProcessType );
|
||||
if( nIndex == m_dictProcessTypeToFactory.InvalidIndex() )
|
||||
return NULL;
|
||||
return m_dictProcessTypeToFactory[ nIndex ];
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Registers a factory for a specific process type
|
||||
//-----------------------------------------------------------------------------
|
||||
void CDirectory::RegisterProcessTypeFactory( const char* pszProcessType, GCFactory_t pfnFactory )
|
||||
{
|
||||
m_dictProcessTypeToFactory.Insert( pszProcessType, pfnFactory );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets the type for a GC given a name
|
||||
//-----------------------------------------------------------------------------
|
||||
int32 CDirectory::GetGCTypeForName( const char *pchName ) const
|
||||
{
|
||||
int nIndex = m_dictRegisteredGCNameToType.Find( pchName );
|
||||
if ( !m_dictRegisteredGCNameToType.IsValidIndex( nIndex ) )
|
||||
return s_nInvalidGCType;
|
||||
|
||||
return m_dictRegisteredGCNameToType[nIndex];
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets the global directory singleton
|
||||
//-----------------------------------------------------------------------------
|
||||
CDirectory *GDirectory()
|
||||
{
|
||||
static CDirectory g_directory;
|
||||
return &g_directory;
|
||||
}
|
||||
|
||||
|
||||
} // namespace GCSDK
|
||||
@@ -0,0 +1,252 @@
|
||||
//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
#include "stdafx.h"
|
||||
#include "framefunction.h"
|
||||
#include "gclogger.h"
|
||||
#include "gcsdk/scheduledfunction.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
CBaseFrameFunction::CBaseFrameFunction( const char *pchName, EFrameType eFrameType ) :
|
||||
m_sName( pchName ),
|
||||
m_EFrameType( eFrameType ),
|
||||
m_nNumCalls( 0 ),
|
||||
m_nTrackedTime( 0 ),
|
||||
m_bRegistered( false )
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
CBaseFrameFunction::~CBaseFrameFunction()
|
||||
{
|
||||
Deregister();
|
||||
}
|
||||
|
||||
//called to handle registering for updates and unregistering. Not registered by default
|
||||
void CBaseFrameFunction::Register()
|
||||
{
|
||||
if( !m_bRegistered )
|
||||
{
|
||||
GFrameFunctionMgr().Register( this );
|
||||
m_bRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CBaseFrameFunction::Deregister()
|
||||
{
|
||||
if( m_bRegistered )
|
||||
{
|
||||
GFrameFunctionMgr().Deregister( this );
|
||||
m_bRegistered = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
CFrameFunctionMgr::CFrameFunctionMgr() :
|
||||
m_nNumProfileFrames( 0 ),
|
||||
m_bCompletedHighPri( false )
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
void CFrameFunctionMgr::Register( CBaseFrameFunction* pFrameFunc )
|
||||
{
|
||||
if( !pFrameFunc )
|
||||
return;
|
||||
|
||||
//see which type this frame function is
|
||||
CBaseFrameFunction::EFrameType eType = pFrameFunc->m_EFrameType;
|
||||
if( eType >= CBaseFrameFunction::k_EFrameType_Count )
|
||||
return;
|
||||
|
||||
//don't allow for duplicates
|
||||
if( m_MainLoopFrameFuncs[ eType ].Find( pFrameFunc ) == m_MainLoopFrameFuncs[ eType ].InvalidIndex() )
|
||||
{
|
||||
m_MainLoopFrameFuncs[ eType ].AddToTail( pFrameFunc );
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
void CFrameFunctionMgr::Deregister( CBaseFrameFunction* pFrameFunc )
|
||||
{
|
||||
if( !pFrameFunc )
|
||||
return;
|
||||
|
||||
//see which type this frame function is
|
||||
CBaseFrameFunction::EFrameType eType = pFrameFunc->m_EFrameType;
|
||||
if( eType >= CBaseFrameFunction::k_EFrameType_Count )
|
||||
return;
|
||||
|
||||
m_MainLoopFrameFuncs[ eType ].FindAndRemove( pFrameFunc );
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
void CFrameFunctionMgr::RunFrame( const CLimitTimer& limitTimer )
|
||||
{
|
||||
VPROF_BUDGET( "CFrameFunctionMgr::RunFrame", VPROF_BUDGETGROUP_STEAM );
|
||||
|
||||
//track the number of frames we've profiled
|
||||
m_nNumProfileFrames++;
|
||||
m_bCompletedHighPri = false;
|
||||
|
||||
//run through each of our 'once a frame' updates
|
||||
RunFrameList( CBaseFrameFunction::k_EFrameType_RunOnce, limitTimer );
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
bool CFrameFunctionMgr::RunFrameTick( const CLimitTimer& limitTimer )
|
||||
{
|
||||
VPROF_BUDGET( "CFrameFunctionMgr::RunFrameTick", VPROF_BUDGETGROUP_STEAM );
|
||||
|
||||
//run high priority if we haven't finished it yet
|
||||
if( !m_bCompletedHighPri )
|
||||
{
|
||||
if( RunFrameList( CBaseFrameFunction::k_EFrameType_RunUntilCompleted, limitTimer ) )
|
||||
{
|
||||
//we need to update another frame
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//we have completed high priority
|
||||
m_bCompletedHighPri = true;
|
||||
}
|
||||
}
|
||||
|
||||
//if we are still here, we have completed high priority, so run until we are done with low priority
|
||||
return RunFrameList( CBaseFrameFunction::k_EFrameType_RunUntilCompletedLowPri, limitTimer );
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
bool CFrameFunctionMgr::RunFrameList( CBaseFrameFunction::EFrameType eType, const CLimitTimer& limitTimer )
|
||||
{
|
||||
bool bResult = false;
|
||||
|
||||
//run through each of our 'once a frame' updates
|
||||
FOR_EACH_VEC( m_MainLoopFrameFuncs[ eType ], nCurrFunc )
|
||||
{
|
||||
CBaseFrameFunction* pFunc = m_MainLoopFrameFuncs[ eType ][ nCurrFunc ];
|
||||
|
||||
uint64 nTimeStart = limitTimer.CMicroSecLeft();
|
||||
|
||||
{
|
||||
VPROF_BUDGET( pFunc->m_sName.Get(), VPROF_BUDGETGROUP_STEAM );
|
||||
bResult |= pFunc->BRun( limitTimer );
|
||||
}
|
||||
|
||||
//track the time spent in this function
|
||||
pFunc->m_nNumCalls++;
|
||||
pFunc->m_nTrackedTime += nTimeStart - limitTimer.CMicroSecLeft();
|
||||
}
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
bool CFrameFunctionMgr::SortFrameFuncByTime( const CBaseFrameFunction* pLhs, const CBaseFrameFunction* pRhs )
|
||||
{
|
||||
//sort by time first, then name if the same time (unlikely)
|
||||
if( pLhs->m_nTrackedTime != pRhs->m_nTrackedTime )
|
||||
return pLhs->m_nTrackedTime > pRhs->m_nTrackedTime;
|
||||
return stricmp( pLhs->m_sName.Get(), pRhs->m_sName.Get() ) < 0;
|
||||
}
|
||||
|
||||
void CFrameFunctionMgr::DumpProfile()
|
||||
{
|
||||
//collect all of our functions and sort them based upon time elapsed
|
||||
CUtlVector< CBaseFrameFunction* > FrameFuncs;
|
||||
uint64 nTotalTime = 0;
|
||||
uint32 nTotalCalls = 0;
|
||||
|
||||
for( uint32 nCurrType = 0; nCurrType < CBaseFrameFunction::k_EFrameType_Count; nCurrType++ )
|
||||
{
|
||||
FOR_EACH_VEC (m_MainLoopFrameFuncs[ nCurrType ], nCurrFunc )
|
||||
{
|
||||
CBaseFrameFunction* pFunc = m_MainLoopFrameFuncs[ nCurrType ][ nCurrFunc ];
|
||||
FrameFuncs.AddToTail( pFunc );
|
||||
nTotalTime += pFunc->m_nTrackedTime;
|
||||
nTotalCalls += pFunc->m_nNumCalls;
|
||||
}
|
||||
}
|
||||
|
||||
double fInvFrame = ( m_nNumProfileFrames > 0 ) ? 1.0 / m_nNumProfileFrames : 1.0;
|
||||
|
||||
std::sort( FrameFuncs.begin(), FrameFuncs.end(), SortFrameFuncByTime );
|
||||
|
||||
//now dump out the timings in a grid
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%s", "Name Time(ms) Calls Time% Time/F Calls/F\n" );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%s", "---------------------------------------- ------------ ---------- ------ ---------- ----------\n" );
|
||||
|
||||
//print out each API
|
||||
FOR_EACH_VEC( FrameFuncs, nFunc )
|
||||
{
|
||||
CBaseFrameFunction* pFunc = FrameFuncs[ nFunc ];
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%-40s %12.2f %10d %5.1f%% %10.3f %10.1f\n",
|
||||
pFunc->m_sName.Get(), pFunc->m_nTrackedTime / 1000.0, pFunc->m_nNumCalls, pFunc->m_nTrackedTime / ( double )nTotalTime, pFunc->m_nTrackedTime * fInvFrame / 1000.0, pFunc->m_nNumCalls * fInvFrame );
|
||||
}
|
||||
|
||||
//print out the footer and totals
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%s", "---------------------------------------- ------------ ---------- ------ ---------- ----------\n" );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%-40s %12.2f %10d %5.1f%% %10.3f %10.1f\n",
|
||||
"Totals", nTotalTime / 1000.0, nTotalCalls, 100.0, nTotalTime * fInvFrame / 1000.0, nTotalCalls * fInvFrame );
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
void CFrameFunctionMgr::ClearProfile()
|
||||
{
|
||||
//run through all of our functions and clear timings
|
||||
for( uint32 nCurrType = 0; nCurrType < CBaseFrameFunction::k_EFrameType_Count; nCurrType++ )
|
||||
{
|
||||
FOR_EACH_VEC (m_MainLoopFrameFuncs[ nCurrType ], nCurrFunc )
|
||||
{
|
||||
CBaseFrameFunction* pFunc = m_MainLoopFrameFuncs[ nCurrType ][ nCurrFunc ];
|
||||
pFunc->m_nTrackedTime = 0;
|
||||
pFunc->m_nNumCalls = 0;
|
||||
}
|
||||
}
|
||||
|
||||
m_nNumProfileFrames = 0;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
CFrameFunctionMgr& GFrameFunctionMgr()
|
||||
{
|
||||
static CFrameFunctionMgr s_Singleton;
|
||||
return s_Singleton;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
//utility class for dumping out the profile results after time has expired
|
||||
static void DumpFrameFunctionProfile()
|
||||
{
|
||||
GFrameFunctionMgr().DumpProfile();
|
||||
}
|
||||
|
||||
GC_CON_COMMAND( framefunc_profile, "Turns on frame function profiling for N seconds and dumps the results" )
|
||||
{
|
||||
if( args.ArgC() < 2 )
|
||||
{
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Proper usage is framefunc_profile <n> where n is the number of seconds to profile for\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
float fSeconds = MAX( 1.0f, atof( args[ 1 ] ) );
|
||||
GFrameFunctionMgr().ClearProfile();
|
||||
static CGlobalScheduledFunction s_DumpProfile;
|
||||
s_DumpProfile.ScheduleMS( DumpFrameFunctionProfile, fSeconds * 1000.0f );
|
||||
}
|
||||
|
||||
} //namespace GCSDK
|
||||
@@ -0,0 +1,68 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "gcsdk/gclogger.h"
|
||||
|
||||
#include "memdbgon.h" // needs to be the last include in the file
|
||||
|
||||
using namespace GCSDK;
|
||||
|
||||
const char *GCConVar::GetName( void ) const
|
||||
{
|
||||
if ( m_strGCName.Length() == 0 )
|
||||
{
|
||||
m_pchBaseName = ConVar::GetName();
|
||||
if( GCSDK::GGCBase() )
|
||||
{
|
||||
m_strGCName.Format( "%s_%u", m_pchBaseName, GCSDK::GGCBase()->GetAppID() );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_strGCName.Format( "%s_gc", m_pchBaseName );
|
||||
}
|
||||
}
|
||||
|
||||
return m_strGCName.String();
|
||||
}
|
||||
|
||||
|
||||
const char *GCConCommand::GetName( void ) const
|
||||
{
|
||||
if ( m_strGCName.Length() == 0 )
|
||||
{
|
||||
m_pchBaseName = ConCommand::GetName();
|
||||
if( GCSDK::GGCBase() )
|
||||
{
|
||||
m_strGCName.Format( "%s_%u", m_pchBaseName, GCSDK::GGCBase()->GetAppID() );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_strGCName.Format( "%s_gc", m_pchBaseName );
|
||||
}
|
||||
}
|
||||
|
||||
return m_strGCName.String();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Checks a command to see if it got enough arguments
|
||||
// Input : nArgs - The minimum required args on the command
|
||||
// args - The arguments passed to the command
|
||||
// command - The command being executed
|
||||
//-----------------------------------------------------------------------------
|
||||
bool BCheckArgs( int nArgs, const CCommand &args, const ConCommandBase &command )
|
||||
{
|
||||
if ( args.ArgC() <= nArgs )
|
||||
{
|
||||
EG_ERROR( SPEW_CONSOLE, "Incorrect number of arguments. %d arguments required, %d were given.\n", nArgs, args.ArgC() - 1 );
|
||||
EG_ERROR( SPEW_CONSOLE, "\t%s\n", command.GetHelpText() );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+4432
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -0,0 +1,586 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Extra functionality on top of CGCClientSharedObjectCache for GCClients
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include "stdafx.h"
|
||||
#include <time.h>
|
||||
#include "gcsdk/gcclient_sharedobjectcache.h"
|
||||
#include "gcsdk_gcmessages.pb.h"
|
||||
#include <typeinfo>
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
//#define SOCDebug(...) Msg( __VA_ARGS__ )
|
||||
#define SOCDebug(...) ((void)0)
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: constructor
|
||||
//----------------------------------------------------------------------------
|
||||
CGCClientSharedObjectContext::CGCClientSharedObjectContext( const CSteamID & steamIDOwner )
|
||||
: m_steamIDOwner( steamIDOwner )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Adds a new Listener to the cache. All objects in the cache will
|
||||
// be sent as create messages to the new Listener
|
||||
//----------------------------------------------------------------------------
|
||||
bool CGCClientSharedObjectContext::BAddListener( ISharedObjectListener *pListener )
|
||||
{
|
||||
if( m_vecListeners.HasElement( pListener ) )
|
||||
return false;
|
||||
|
||||
m_vecListeners.AddToTail( pListener );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Removes a Listener from the cache. All objects in the cache
|
||||
// will have destroy messages sent for them to the new Listener.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CGCClientSharedObjectContext::BRemoveListener( ISharedObjectListener *pListener )
|
||||
{
|
||||
return m_vecListeners.FindAndRemove( pListener );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Send created/updated/destroyed calls on to all the listeners in the
|
||||
// context
|
||||
//----------------------------------------------------------------------------
|
||||
void CGCClientSharedObjectContext::SOCreated( const CSharedObject *pObject, ESOCacheEvent eEvent ) const
|
||||
{
|
||||
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
|
||||
FOR_EACH_VEC( m_vecListeners, nListener )
|
||||
{
|
||||
m_vecListeners[nListener]->SOCreated( m_steamIDOwner, pObject, eEvent );
|
||||
}
|
||||
}
|
||||
|
||||
void CGCClientSharedObjectContext::PreSOUpdate( ESOCacheEvent eEvent ) const
|
||||
{
|
||||
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
|
||||
FOR_EACH_VEC( m_vecListeners, nListener )
|
||||
{
|
||||
m_vecListeners[nListener]->PreSOUpdate( m_steamIDOwner, eEvent );
|
||||
}
|
||||
}
|
||||
|
||||
void CGCClientSharedObjectContext::SOUpdated( const CSharedObject *pObject, ESOCacheEvent eEvent ) const
|
||||
{
|
||||
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
|
||||
FOR_EACH_VEC( m_vecListeners, nListener )
|
||||
{
|
||||
m_vecListeners[nListener]->SOUpdated( m_steamIDOwner, pObject, eEvent );
|
||||
}
|
||||
}
|
||||
|
||||
void CGCClientSharedObjectContext::PostSOUpdate( ESOCacheEvent eEvent ) const
|
||||
{
|
||||
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
|
||||
FOR_EACH_VEC( m_vecListeners, nListener )
|
||||
{
|
||||
m_vecListeners[nListener]->PostSOUpdate( m_steamIDOwner, eEvent );
|
||||
}
|
||||
}
|
||||
|
||||
void CGCClientSharedObjectContext::SODestroyed( const CSharedObject *pObject, ESOCacheEvent eEvent ) const
|
||||
{
|
||||
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
|
||||
FOR_EACH_VEC( m_vecListeners, nListener )
|
||||
{
|
||||
m_vecListeners[nListener]->SODestroyed( m_steamIDOwner, pObject, eEvent );
|
||||
}
|
||||
}
|
||||
|
||||
void CGCClientSharedObjectContext::SOCacheSubscribed( const CSteamID & steamIDOwner, ESOCacheEvent eEvent ) const
|
||||
{
|
||||
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
|
||||
FOR_EACH_VEC( m_vecListeners, nListener )
|
||||
{
|
||||
m_vecListeners[nListener]->SOCacheSubscribed( steamIDOwner, eEvent );
|
||||
}
|
||||
}
|
||||
|
||||
void CGCClientSharedObjectContext::SOCacheUnsubscribed( const CSteamID & steamIDOwner, ESOCacheEvent eEvent ) const
|
||||
{
|
||||
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
|
||||
FOR_EACH_VEC( m_vecListeners, nListener )
|
||||
{
|
||||
m_vecListeners[nListener]->SOCacheUnsubscribed( steamIDOwner, eEvent );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//----------------------------------------------------------------------------
|
||||
CGCClientSharedObjectTypeCache::CGCClientSharedObjectTypeCache( int nTypeID, const CGCClientSharedObjectContext & context )
|
||||
: m_context( context ), CSharedObjectTypeCache( nTypeID )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//----------------------------------------------------------------------------
|
||||
CGCClientSharedObjectTypeCache::~CGCClientSharedObjectTypeCache()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Parses a cache subscribed message.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CGCClientSharedObjectTypeCache::BParseCacheSubscribedMsg( const CMsgSOCacheSubscribed_SubscribedType & msg, CUtlVector<CSharedObject*> &vecCreatedObjects, CUtlVector<CSharedObject*> &vecUpdatedObjects, CUtlVector<CSharedObject*> &vecObjectsToDestroy )
|
||||
{
|
||||
CSharedObjectVec vecUntouchedObjects;
|
||||
for ( uint32 i = 0; i < GetCount(); i++ )
|
||||
{
|
||||
vecUntouchedObjects.AddToTail( GetObject( i ) );
|
||||
}
|
||||
|
||||
for( uint16 usObject = 0; usObject < msg.object_data_size(); usObject++ )
|
||||
{
|
||||
bool bUpdatedExisting = false;
|
||||
CSharedObject *pObject = BCreateFromMsg( msg.object_data( usObject ).data(), msg.object_data( usObject ).size(), &bUpdatedExisting );
|
||||
if ( pObject == NULL)
|
||||
{
|
||||
Assert( pObject );
|
||||
return false;
|
||||
}
|
||||
|
||||
// if an object was updated, remove it from the untouched list
|
||||
if ( bUpdatedExisting )
|
||||
{
|
||||
int index = vecUntouchedObjects.Find( pObject );
|
||||
if ( index != vecUntouchedObjects.InvalidIndex() )
|
||||
{
|
||||
vecUntouchedObjects[index] = NULL;
|
||||
}
|
||||
vecUpdatedObjects.AddToTail( pObject );
|
||||
}
|
||||
else
|
||||
{
|
||||
vecCreatedObjects.AddToTail( pObject );
|
||||
}
|
||||
}
|
||||
|
||||
// all objects that weren't in the SubscribedMsg should be destroyed
|
||||
for ( int i = 0; i < vecUntouchedObjects.Count(); i++ )
|
||||
{
|
||||
if ( vecUntouchedObjects[i] == NULL )
|
||||
continue;
|
||||
|
||||
CSharedObject *pObject = RemoveObject( *vecUntouchedObjects[i] );
|
||||
Assert( pObject );
|
||||
if( pObject )
|
||||
vecObjectsToDestroy.AddToTail( pObject );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGCClientSharedObjectTypeCache::RemoveAllObjects( CUtlVector<CSharedObject*> &vecObjects )
|
||||
{
|
||||
|
||||
// Go in reverse order to avoid O(n^2) shifting the items in the array
|
||||
for ( int i = GetCount() - 1; i >= 0; i-- )
|
||||
{
|
||||
CSharedObject *pObject = RemoveObjectByIndex( i );
|
||||
Assert( pObject );
|
||||
if ( pObject )
|
||||
vecObjects.AddToTail( pObject );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Processes a received create message for an object of this type on
|
||||
// the client/gameserver
|
||||
//----------------------------------------------------------------------------
|
||||
CSharedObject *CGCClientSharedObjectTypeCache::BCreateFromMsg( const void *pvData, uint32 unSize, bool *bUpdatedExisting )
|
||||
{
|
||||
CUtlBuffer bufCreate( pvData, unSize, CUtlBuffer::READ_ONLY );
|
||||
CSharedObject *pNewObj = CSharedObject::Create( GetTypeID() );
|
||||
Assert( pNewObj );
|
||||
if( !pNewObj )
|
||||
{
|
||||
EmitError( SPEW_SHAREDOBJ, "Unable to create object of type %d\n", GetTypeID() );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if( !pNewObj->BParseFromMessage( bufCreate ) )
|
||||
{
|
||||
delete pNewObj;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Existing object?
|
||||
CSharedObject *pObj = FindSharedObject( *pNewObj );
|
||||
if( pObj )
|
||||
{
|
||||
pObj->Copy( *pNewObj );
|
||||
delete pNewObj;
|
||||
if ( bUpdatedExisting )
|
||||
{
|
||||
*bUpdatedExisting = true;
|
||||
}
|
||||
return pObj;
|
||||
}
|
||||
|
||||
// New object
|
||||
AddObject( pNewObj );
|
||||
if ( bUpdatedExisting )
|
||||
{
|
||||
*bUpdatedExisting = false;
|
||||
}
|
||||
return pNewObj;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Processes a received destroy message for an object of this type on
|
||||
// the client/gameserver
|
||||
//----------------------------------------------------------------------------
|
||||
bool CGCClientSharedObjectTypeCache::BDestroyFromMsg( const void *pvData, uint32 unSize )
|
||||
{
|
||||
CUtlBuffer bufDestroy( pvData, unSize, CUtlBuffer::READ_ONLY );
|
||||
CSharedObject *pIndexObj = CSharedObject::Create( GetTypeID() );
|
||||
if( !pIndexObj->BParseFromMessage( bufDestroy ) )
|
||||
{
|
||||
delete pIndexObj;
|
||||
return false;
|
||||
}
|
||||
|
||||
CSharedObject *pObject = RemoveObject( *pIndexObj );
|
||||
if( pObject )
|
||||
{
|
||||
m_context.SODestroyed( pObject, eSOCacheEvent_Incremental );
|
||||
delete pObject;
|
||||
}
|
||||
|
||||
delete pIndexObj;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Processes a received destroy message for an object of this type on
|
||||
// the client/gameserver
|
||||
//----------------------------------------------------------------------------
|
||||
bool CGCClientSharedObjectTypeCache::BUpdateFromMsg( const void *pvData, uint32 unSize )
|
||||
{
|
||||
CUtlBuffer bufUpdate( pvData, unSize, CUtlBuffer::READ_ONLY );
|
||||
CSharedObject *pIndexObj = CSharedObject::Create( GetTypeID() );
|
||||
AssertMsg1( pIndexObj, "Unable to create index object of type %d", GetTypeID() );
|
||||
if( !pIndexObj )
|
||||
return false;
|
||||
if( !pIndexObj->BParseFromMessage( bufUpdate ) )
|
||||
{
|
||||
delete pIndexObj;
|
||||
return false;
|
||||
}
|
||||
|
||||
CSharedObject *pObj = FindSharedObject( *pIndexObj );
|
||||
bool bRet = false;
|
||||
if( pObj )
|
||||
{
|
||||
bufUpdate.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
|
||||
|
||||
bRet = pObj->BUpdateFromNetwork( *pIndexObj );
|
||||
m_context.SOUpdated( pObj, eSOCacheEvent_Incremental );
|
||||
}
|
||||
|
||||
delete pIndexObj;
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//----------------------------------------------------------------------------
|
||||
CGCClientSharedObjectCache::CGCClientSharedObjectCache( const CSteamID & steamIDOwner )
|
||||
: m_context( steamIDOwner ),
|
||||
m_bInitialized( false ),
|
||||
m_bSubscribed( false )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//----------------------------------------------------------------------------
|
||||
CGCClientSharedObjectCache::~CGCClientSharedObjectCache()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Process an incoming create message on a client/gameserver.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CGCClientSharedObjectCache::BParseCacheSubscribedMsg( const CMsgSOCacheSubscribed & msg )
|
||||
{
|
||||
|
||||
// Assume all type caches will be untouched
|
||||
CUtlVector<int> vecUntouchedTypes;
|
||||
for ( int i = FirstTypeCacheIndex(); i != InvalidTypeCacheIndex(); i = NextTypeCacheIndex( i ) )
|
||||
{
|
||||
CSharedObjectTypeCache *pTypeCache = GetTypeCacheByIndex( i );
|
||||
if ( pTypeCache )
|
||||
{
|
||||
vecUntouchedTypes.AddToTail( pTypeCache->GetTypeID() );
|
||||
}
|
||||
}
|
||||
|
||||
// List of objects created, updated, and removed
|
||||
CUtlVector<CSharedObject*> vecCreatedObjects;
|
||||
CUtlVector<CSharedObject*> vecUpdatedObjects;
|
||||
CUtlVector<CSharedObject*> vecObjectsToDestroy;
|
||||
|
||||
bool bResult = true;
|
||||
|
||||
// Scan types in message
|
||||
for( uint16 usObject = 0; usObject < msg.objects_size(); usObject++ )
|
||||
{
|
||||
const CMsgSOCacheSubscribed_SubscribedType & msgType = msg.objects( usObject );
|
||||
|
||||
// Find or create the type
|
||||
CGCClientSharedObjectTypeCache *pTypeCache = CreateTypeCache( msgType.type_id() );
|
||||
if ( pTypeCache )
|
||||
{
|
||||
int index = vecUntouchedTypes.Find( pTypeCache->GetTypeID() );
|
||||
if ( index != vecUntouchedTypes.InvalidIndex() )
|
||||
{
|
||||
vecUntouchedTypes[index] = -1;
|
||||
}
|
||||
}
|
||||
Assert( pTypeCache );
|
||||
if( !pTypeCache || !pTypeCache->BParseCacheSubscribedMsg( msgType, vecCreatedObjects, vecUpdatedObjects, vecObjectsToDestroy ) )
|
||||
bResult = false;
|
||||
}
|
||||
|
||||
// any type caches that weren't in the SubscribedMsg should be cleared
|
||||
for ( int i = FirstTypeCacheIndex(); i != InvalidTypeCacheIndex(); i = NextTypeCacheIndex( i ) )
|
||||
{
|
||||
CGCClientSharedObjectTypeCache *pTypeCache = GetTypeCacheByIndex( i );
|
||||
if ( vecUntouchedTypes.Find( pTypeCache->GetTypeID() ) != vecUntouchedTypes.InvalidIndex() )
|
||||
{
|
||||
pTypeCache->RemoveAllObjects( vecObjectsToDestroy );
|
||||
}
|
||||
}
|
||||
|
||||
// Which event is happening?
|
||||
ESOCacheEvent eNotificationEvent = eSOCacheEvent_Subscribed;
|
||||
if ( m_bSubscribed )
|
||||
eNotificationEvent = eSOCacheEvent_Resubscribed;
|
||||
|
||||
// Set version, assuming we didn't have any problems. If we hit any problems,
|
||||
// we want to force a refresh
|
||||
if ( bResult )
|
||||
SetVersion( msg.version() );
|
||||
|
||||
// Mark that the cache has been initialized by the server
|
||||
m_bInitialized = true;
|
||||
m_bSubscribed = true;
|
||||
|
||||
//
|
||||
// Send notifications
|
||||
//
|
||||
|
||||
// Initial cache subscribed
|
||||
m_context.SOCacheSubscribed( GetOwner(), eNotificationEvent );
|
||||
|
||||
// Deletions
|
||||
for ( int i = 0 ; i < vecObjectsToDestroy.Count() ; ++i )
|
||||
{
|
||||
m_context.SODestroyed( vecObjectsToDestroy[i], eNotificationEvent );
|
||||
delete vecObjectsToDestroy[i];
|
||||
}
|
||||
|
||||
// Updates
|
||||
for ( int i = 0 ; i < vecUpdatedObjects.Count() ; ++i )
|
||||
{
|
||||
m_context.SOUpdated( vecUpdatedObjects[i], eNotificationEvent );
|
||||
}
|
||||
|
||||
// Created
|
||||
for ( int i = 0 ; i < vecCreatedObjects.Count() ; ++i )
|
||||
{
|
||||
m_context.SOUpdated( vecCreatedObjects[i], eNotificationEvent );
|
||||
}
|
||||
|
||||
// Return true if everything parsed OK, or false
|
||||
// if we had at least one failure
|
||||
return bResult;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Process an incoming create message on a client/gameserver.
|
||||
//----------------------------------------------------------------------------
|
||||
void CGCClientSharedObjectCache::NotifyUnsubscribe()
|
||||
{
|
||||
if ( m_bSubscribed )
|
||||
{
|
||||
m_bSubscribed = false;
|
||||
m_context.SOCacheUnsubscribed( GetOwner(), eSOCacheEvent_Unsubscribed );
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertMsg( m_bSubscribed, "GC Sending us Unsubscribed message when we weren't subscribed" ); // Might not be a bug, but something worth checking
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: GC is telling us that the version we have is up-to-date,a nd we are subscribed
|
||||
//----------------------------------------------------------------------------
|
||||
void CGCClientSharedObjectCache::NotifyResubscribedUpToDate()
|
||||
{
|
||||
if ( !m_bSubscribed )
|
||||
{
|
||||
Assert( m_bInitialized );
|
||||
m_bSubscribed = true;
|
||||
m_context.SOCacheSubscribed( GetOwner(), eSOCacheEvent_Subscribed );
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertMsg( m_bSubscribed, "Got NotifyResubscribedUpToDate when we were already subscribed?" ); // Might not be a bug, but something worth checking
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Process an incoming create message on a client/gameserver.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CGCClientSharedObjectCache::BCreateFromMsg( int nTypeID, const void *pvData, uint32 unSize )
|
||||
{
|
||||
// We should be subscribed
|
||||
if ( !m_bInitialized || !m_bSubscribed )
|
||||
{
|
||||
// Note: We can go down and come back up without the GC knowing this.
|
||||
// So this can happen
|
||||
//Assert( m_bInitialized );
|
||||
//Assert( m_bSubscribed );
|
||||
//EmitWarning( SPEW_SHAREDOBJ, 1, "Received SOCache incremental update for cache we were not subscribed to (object type %d)\n", nTypeID );
|
||||
}
|
||||
|
||||
// Locate / create the type cache
|
||||
CGCClientSharedObjectTypeCache *pTypeCache = CreateTypeCache( nTypeID );
|
||||
|
||||
// Create the message or update existing
|
||||
bool bUpdatedExisting = false;
|
||||
CSharedObject *pObject = pTypeCache->BCreateFromMsg( pvData, unSize, &bUpdatedExisting );
|
||||
if ( pObject == NULL )
|
||||
return false;
|
||||
|
||||
// Send notifications to listeners
|
||||
if ( bUpdatedExisting )
|
||||
{
|
||||
// This can happen --- see comment at the top of this function
|
||||
//Assert( !bUpdatedExisting ); // shouldn't the GC know what it's already sent us? This is weird
|
||||
m_context.SOUpdated( pObject, eSOCacheEvent_Incremental );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_context.SOCreated( pObject, eSOCacheEvent_Incremental );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Processes an incoming destroy message on a client/gameserver.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CGCClientSharedObjectCache::BDestroyFromMsg( int nTypeID, const void *pvData, uint32 unSize )
|
||||
{
|
||||
CGCClientSharedObjectTypeCache *pTypeCache = FindTypeCache( nTypeID );
|
||||
if( pTypeCache )
|
||||
{
|
||||
return pTypeCache->BDestroyFromMsg( pvData, unSize );
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Processes an incoming update message on a client/gameserver.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CGCClientSharedObjectCache::BUpdateFromMsg( int nTypeID, const void *pvData, uint32 unSize )
|
||||
{
|
||||
CGCClientSharedObjectTypeCache *pTypeCache = FindTypeCache( nTypeID );
|
||||
if( pTypeCache )
|
||||
{
|
||||
return pTypeCache->BUpdateFromMsg( pvData, unSize );
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Adds a listener object to be notified of object changes in this
|
||||
// cache. The shared object cache does not own this object and will
|
||||
// not free it.
|
||||
//----------------------------------------------------------------------------
|
||||
void CGCClientSharedObjectCache::AddListener( ISharedObjectListener *pListener )
|
||||
{
|
||||
Assert( pListener );
|
||||
if ( !m_context.BAddListener( pListener ) )
|
||||
return; // was already listening, no action needed
|
||||
|
||||
SOCDebug( "[%s] Adding listener %s\n", GetOwner().Render(), typeid( *pListener ).name() );
|
||||
|
||||
// If we're already subscribed, then immediately send notifications
|
||||
if( BIsSubscribed() )
|
||||
{
|
||||
pListener->SOCacheSubscribed( GetOwner(), eSOCacheEvent_ListenerAdded );
|
||||
for ( int i = FirstTypeCacheIndex(); i != InvalidTypeCacheIndex(); i = NextTypeCacheIndex( i ) )
|
||||
{
|
||||
CGCClientSharedObjectTypeCache *pTypeCache = GetTypeCacheByIndex( i );
|
||||
for ( uint32 j = 0 ; j < pTypeCache->GetCount() ; ++j )
|
||||
{
|
||||
CSharedObject *pObject = pTypeCache->GetObject( j );
|
||||
pListener->SOCreated( GetOwner(), pObject, eSOCacheEvent_ListenerAdded );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Removes a listener object from the list to be notified of changes
|
||||
// to this object cache.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CGCClientSharedObjectCache::RemoveListener( ISharedObjectListener *pListener )
|
||||
{
|
||||
Assert( pListener );
|
||||
if ( !m_context.BRemoveListener( pListener ) )
|
||||
return false; // wasn't already listening, nothing to do
|
||||
|
||||
SOCDebug( "[%s] Removing listener %s\n", GetOwner().Render(), typeid( *pListener ).name() );
|
||||
|
||||
// If we were subscribed, then the listener's last subscribe notification
|
||||
// was a "you are subscribed." Send him an unsubscribed notification
|
||||
// so he doesn't think he's still subscribed.
|
||||
if( BIsSubscribed() )
|
||||
{
|
||||
pListener->SOCacheUnsubscribed( GetOwner(), eSOCacheEvent_ListenerRemoved );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace GCSDK
|
||||
@@ -0,0 +1,45 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Enum name to string and vice versa code
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "enumutils.h"
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
#ifdef GC
|
||||
ENUMSTRINGS_START( EForeignKeyAction )
|
||||
{k_EForeignKeyActionNoAction, "NO ACTION" },
|
||||
{k_EForeignKeyActionCascade, "CASCADE" },
|
||||
{k_EForeignKeyActionSetNULL, "SET NULL" },
|
||||
ENUMSTRINGS_REVERSE( EForeignKeyAction, k_EForeignKeyActionNoAction )
|
||||
|
||||
ENUMSTRINGS_START( EGCSQLType )
|
||||
{k_EGCSQLTypeInvalid, "k_EGCSQLTypeInvalid" },
|
||||
{k_EGCSQLType_Blob, "k_EGCSQLType_Blob" },
|
||||
{k_EGCSQLType_String, "k_EGCSQLType_String" },
|
||||
{k_EGCSQLType_int8, "k_EGCSQLType_int8" },
|
||||
{k_EGCSQLType_int16, "k_EGCSQLType_int16" },
|
||||
{k_EGCSQLType_int32, "k_EGCSQLType_int32" },
|
||||
{k_EGCSQLType_int64, "k_EGCSQLType_int64" },
|
||||
{k_EGCSQLType_float, "k_EGCSQLType_float" },
|
||||
{k_EGCSQLType_double, "k_EGCSQLType_double" },
|
||||
{k_EGCSQLType_Image, "k_EGCSQLType_Image" },
|
||||
ENUMSTRINGS_REVERSE( EGCSQLType, k_EGCSQLTypeInvalid )
|
||||
#endif
|
||||
|
||||
ENUMSTRINGS_START( EUniverse )
|
||||
{ k_EUniverseInvalid, "Invalid" },
|
||||
{ k_EUniversePublic, "Public" },
|
||||
{ k_EUniverseBeta, "Beta" },
|
||||
{ k_EUniverseInternal, "Internal" },
|
||||
{ k_EUniverseDev, "Dev" },
|
||||
ENUMSTRINGS_REVERSE( EUniverse, k_EUniverseInvalid )
|
||||
|
||||
} // namespace GCSDK
|
||||
@@ -0,0 +1,190 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Network dirty field marker for shared objects
|
||||
//
|
||||
//=============================================================================
|
||||
#include "stdafx.h"
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
CSharedObjectDirtyFieldList::CSharedObjectDirtyFieldList( CSharedObject *obj )
|
||||
: m_obj( obj )
|
||||
, m_firstFieldBits( 0 )
|
||||
, m_pExtendedFields( NULL )
|
||||
{
|
||||
}
|
||||
|
||||
CSharedObjectDirtyFieldList::~CSharedObjectDirtyFieldList()
|
||||
{
|
||||
if ( m_pExtendedFields )
|
||||
{
|
||||
delete m_pExtendedFields;
|
||||
}
|
||||
}
|
||||
|
||||
CSharedObject *CSharedObjectDirtyFieldList::Obj() const
|
||||
{
|
||||
return m_obj;
|
||||
}
|
||||
|
||||
void CSharedObjectDirtyFieldList::DirtyField( int index )
|
||||
{
|
||||
// if the field index fits within our dirty fields struct, set a bit to track it
|
||||
if ( index < 32 )
|
||||
{
|
||||
m_firstFieldBits |= (1 << index);
|
||||
}
|
||||
// if the field index is too big, store it in our backup structure; this is less efficient but more
|
||||
// flexible, especially when dealing with fields that themselves contain flags so would be interpreted
|
||||
// as huge numbers
|
||||
else
|
||||
{
|
||||
if ( !m_pExtendedFields )
|
||||
m_pExtendedFields = new CUtlVector<int>;
|
||||
|
||||
if ( !m_pExtendedFields->HasElement( index ) )
|
||||
m_pExtendedFields->AddToTail( index );
|
||||
}
|
||||
}
|
||||
|
||||
void CSharedObjectDirtyFieldList::GetDirtyFieldSet( CUtlVector<int> &fieldSet ) const
|
||||
{
|
||||
fieldSet.Purge();
|
||||
if( !m_firstFieldBits && !m_pExtendedFields )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if( m_firstFieldBits )
|
||||
{
|
||||
for( int i = 0; i < 32; i++ )
|
||||
{
|
||||
// handle dirty bits
|
||||
if( m_firstFieldBits & ( 1 << i ) )
|
||||
{
|
||||
fieldSet.AddToTail( i );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle higher order dirty-fields list if present
|
||||
if ( m_pExtendedFields )
|
||||
{
|
||||
fieldSet.AddVectorToTail( *m_pExtendedFields );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
CSharedObjectDirtyList::CSharedObjectDirtyList()
|
||||
{
|
||||
}
|
||||
|
||||
CSharedObjectDirtyList::~CSharedObjectDirtyList()
|
||||
{
|
||||
}
|
||||
|
||||
void CSharedObjectDirtyList::DirtyObjectField( CSharedObject *obj, int field )
|
||||
{
|
||||
MEM_ALLOC_CREDIT_CLASS();
|
||||
Assert( obj != NULL );
|
||||
if (!obj)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int index = FindIndexByObj( obj );
|
||||
if ( index == InvalidIndex() )
|
||||
{
|
||||
index = m_sharedObjectDirtyFieldList.AddToTail( CSharedObjectDirtyFieldList( obj ) );
|
||||
}
|
||||
|
||||
m_sharedObjectDirtyFieldList[index].DirtyField( field );
|
||||
}
|
||||
|
||||
int CSharedObjectDirtyList::FindIndexByObj( const CSharedObject *pObj ) const
|
||||
{
|
||||
// Slow method for now, we can speed this up with a map later
|
||||
for ( int i = 0; i < m_sharedObjectDirtyFieldList.Count(); ++i )
|
||||
{
|
||||
if( m_sharedObjectDirtyFieldList[i].Obj() == pObj )
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidIndex();
|
||||
}
|
||||
|
||||
bool CSharedObjectDirtyList::HasElement( const CSharedObject *pObj ) const
|
||||
{
|
||||
return FindIndexByObj( pObj ) != InvalidIndex();
|
||||
}
|
||||
|
||||
bool CSharedObjectDirtyList::GetDirtyFieldSetByIndex( int index, CSharedObject **ppObj, CUtlVector<int> &fieldSet ) const
|
||||
{
|
||||
VPROF_BUDGET( "CSharedObjectDirtyList::GetDirtyFieldSetByIndex", VPROF_BUDGETGROUP_STEAM );
|
||||
if( !m_sharedObjectDirtyFieldList.IsValidIndex( index ) )
|
||||
{
|
||||
fieldSet.Purge();
|
||||
if( ppObj )
|
||||
{
|
||||
*ppObj = NULL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const CSharedObjectDirtyFieldList &fieldList = m_sharedObjectDirtyFieldList[index];
|
||||
if( ppObj )
|
||||
{
|
||||
*ppObj = fieldList.Obj();
|
||||
}
|
||||
fieldList.GetDirtyFieldSet( fieldSet );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CSharedObjectDirtyList::GetDirtyFieldSetByObj( CSharedObject *pObj, CUtlVector<int> &fieldSet )
|
||||
{
|
||||
int index = FindIndexByObj( pObj );
|
||||
if ( index == InvalidIndex() )
|
||||
{
|
||||
fieldSet.Purge();
|
||||
return false;
|
||||
}
|
||||
|
||||
CSharedObjectDirtyFieldList &fieldList = m_sharedObjectDirtyFieldList[index];
|
||||
fieldList.GetDirtyFieldSet( fieldSet );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CSharedObjectDirtyList::FindAndRemove( CSharedObject *pObj )
|
||||
{
|
||||
int index = FindIndexByObj( pObj );
|
||||
if ( index == InvalidIndex() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_sharedObjectDirtyFieldList.Remove( index );
|
||||
return true;
|
||||
}
|
||||
|
||||
void CSharedObjectDirtyList::RemoveAll()
|
||||
{
|
||||
m_sharedObjectDirtyFieldList.RemoveAll();
|
||||
}
|
||||
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
void CSharedObjectDirtyList::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
VALIDATE_SCOPE();
|
||||
|
||||
ValidateObj( m_sharedObjectDirtyFieldList );
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace GCSDK
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Holds a pointer to the GC's host's interface
|
||||
//
|
||||
//=============================================================================
|
||||
#include "stdafx.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
IGameCoordinatorHost *g_pGCHost = NULL;
|
||||
|
||||
IGameCoordinatorHost *GGCHost()
|
||||
{
|
||||
return g_pGCHost;
|
||||
}
|
||||
|
||||
|
||||
void SetGCHost( IGameCoordinatorHost *pHost )
|
||||
{
|
||||
g_pGCHost = pHost;
|
||||
}
|
||||
|
||||
} // namespace GCSDK
|
||||
File diff suppressed because it is too large
Load Diff
+1590
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,93 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "gcsdk/enumutils.h"
|
||||
#include "gcsdk/gcbase.h"
|
||||
#include "gcsdk/http.h"
|
||||
#include "gcsdk/job.h"
|
||||
#include "steam/isteamuserstats.h"
|
||||
#include "gcleaderboardapi.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
using namespace GCSDK;
|
||||
|
||||
// @note Tom Bui: copied from steam
|
||||
ENUMSTRINGS_START( ELeaderboardSortMethod )
|
||||
{ k_ELeaderboardSortMethodNone, "" },
|
||||
{ k_ELeaderboardSortMethodAscending, "Ascending" },
|
||||
{ k_ELeaderboardSortMethodDescending, "Descending" },
|
||||
ENUMSTRINGS_REVERSE( ELeaderboardSortMethod, k_ELeaderboardSortMethodNone )
|
||||
|
||||
ENUMSTRINGS_START( ELeaderboardDisplayType )
|
||||
{ k_ELeaderboardDisplayTypeNone, "" },
|
||||
{ k_ELeaderboardDisplayTypeNumeric, "Numeric" },
|
||||
{ k_ELeaderboardDisplayTypeTimeSeconds, "Seconds" },
|
||||
{ k_ELeaderboardDisplayTypeTimeMilliSeconds, "MilliSeconds" },
|
||||
ENUMSTRINGS_REVERSE( ELeaderboardDisplayType, k_ELeaderboardDisplayTypeNone )
|
||||
|
||||
ENUMSTRINGS_START( ELeaderboardUploadScoreMethod )
|
||||
{ k_ELeaderboardUploadScoreMethodNone, "" },
|
||||
{ k_ELeaderboardUploadScoreMethodKeepBest, "KeepBest" },
|
||||
{ k_ELeaderboardUploadScoreMethodForceUpdate, "ForceUpdate" },
|
||||
ENUMSTRINGS_REVERSE( ELeaderboardUploadScoreMethod, k_ELeaderboardUploadScoreMethodNone )
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
uint32 Leaderboard_YieldingFind( const char *pName, ELeaderboardSortMethod eLeaderboardSortMethod, ELeaderboardDisplayType eLeaderboardDisplayType, bool bCreateIfNotFound )
|
||||
{
|
||||
CSteamAPIRequest apiRequest( k_EHTTPMethodPOST, "ISteamLeaderboards", "FindOrCreateLeaderboard", 1 );
|
||||
apiRequest.SetPOSTParamUInt32( "appid", GGCBase()->GetAppID() );
|
||||
apiRequest.SetPOSTParamString( "name", pName );
|
||||
|
||||
apiRequest.SetPOSTParamString( "sortmethod", PchNameFromELeaderboardSortMethod( eLeaderboardSortMethod ) );
|
||||
apiRequest.SetPOSTParamString( "displaytype", PchNameFromELeaderboardDisplayType( eLeaderboardDisplayType ) );
|
||||
apiRequest.SetPOSTParamBool( "createifnotfound", bCreateIfNotFound );
|
||||
|
||||
KeyValuesAD kvAPIResponse( "response" );
|
||||
const EResult eCallResult = GGCBase()->YieldingSendHTTPRequestKV( &apiRequest, kvAPIResponse );
|
||||
const EResult eAPIResult = eCallResult == k_EResultOK ? static_cast<EResult>( kvAPIResponse->GetInt( "result", k_EResultFail ) ) : k_EResultFail;
|
||||
if ( eAPIResult == k_EResultOK )
|
||||
{
|
||||
KeyValues *pKVEntry = kvAPIResponse->FindKey( pName );
|
||||
if ( pKVEntry )
|
||||
{
|
||||
return pKVEntry->GetInt( "leaderBoardID", kInvalidLeaderboardID );
|
||||
}
|
||||
}
|
||||
return kInvalidLeaderboardID;
|
||||
}
|
||||
|
||||
bool Leaderboard_YieldingSetScore( uint32 unLeaderboardID, const CSteamID &steamID, ELeaderboardUploadScoreMethod eLeaderboardUploadScoreMethod, int score )
|
||||
{
|
||||
Assert( unLeaderboardID != kInvalidLeaderboardID );
|
||||
if ( unLeaderboardID == kInvalidLeaderboardID )
|
||||
return false;
|
||||
|
||||
CSteamAPIRequest apiRequest( k_EHTTPMethodPOST, "ISteamLeaderboards", "SetLeaderboardScore", 1 );
|
||||
apiRequest.SetPOSTParamUInt32( "appid", GGCBase()->GetAppID() );
|
||||
apiRequest.SetPOSTParamUInt32( "leaderboardid", unLeaderboardID );
|
||||
apiRequest.SetPOSTParamUInt64( "steamid", steamID.ConvertToUint64() );
|
||||
apiRequest.SetPOSTParamInt32( "score", score );
|
||||
|
||||
apiRequest.SetPOSTParamString( "scoremethod", PchNameFromELeaderboardUploadScoreMethod( eLeaderboardUploadScoreMethod ) );
|
||||
|
||||
KeyValuesAD kvAPIResponse( "response" );
|
||||
const EResult eCallResult = GGCBase()->YieldingSendHTTPRequestKV( &apiRequest, kvAPIResponse );
|
||||
const EResult eAPIResult = eCallResult == k_EResultOK ? static_cast<EResult>( kvAPIResponse->GetInt( "result", k_EResultFail ) ) : k_EResultFail;
|
||||
if ( eAPIResult == k_EResultOK )
|
||||
return true;
|
||||
|
||||
EmitError( SPEW_GC, __FUNCTION__ ": error code %u/%u setting leaderboard %u to %i (%s) for user '%s'.\n",
|
||||
eCallResult, eAPIResult, unLeaderboardID, score, PchNameFromELeaderboardUploadScoreMethod( eLeaderboardUploadScoreMethod ), steamID.Render() );
|
||||
return false;
|
||||
}
|
||||
|
||||
}; // namespace GCSDK
|
||||
@@ -0,0 +1,263 @@
|
||||
//========= Copyright (c), Valve LLC, All rights reserved. ============
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "steamextra/gamecoordinator/igamecoordinatorhost.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Maximum length of a sprintf'ed logging message.
|
||||
//-----------------------------------------------------------------------------
|
||||
const int MAX_LOGGING_MESSAGE_LENGTH = 2048;
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
int g_nMaxSpewLevel = 4;
|
||||
int g_nMaxLogLevel = 4;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Sends an event back to the GC host for logging
|
||||
// Input: pchGroupName - group to log to
|
||||
// spewType - the type of the message
|
||||
// iLevel - level of spew ( 0..4 )
|
||||
// iLevelLog - level for logging
|
||||
// pchMsg - printf format
|
||||
//-----------------------------------------------------------------------------
|
||||
void EmitBaseMessageV( const char *pchGroupName, SpewType_t spewType, int iSpewLevel, int iLevelLog, const char *pchMsg, va_list vaArgs )
|
||||
{
|
||||
VPROF_BUDGET( "GCHost", VPROF_BUDGETGROUP_STEAM );
|
||||
|
||||
char pchBuf[ MAX_LOGGING_MESSAGE_LENGTH ];
|
||||
Q_vsnprintf( pchBuf, MAX_LOGGING_MESSAGE_LENGTH, pchMsg, vaArgs );
|
||||
|
||||
#ifdef GC
|
||||
// !FIXME! DOTAMERGE
|
||||
// // If this is coming from the context of a job, then allow that job to
|
||||
// // override our emit via a specified handler.
|
||||
// if ( g_pJobCur )
|
||||
// {
|
||||
// IJobEmitSpewHandler *pHandler = g_pJobCur->GetEmitSpewHandler();
|
||||
// if ( pHandler )
|
||||
// {
|
||||
// bool bOutputSpew = pHandler->OnEmitSpew( pchGroupName, spewType, iSpewLevel, iLevelLog, pchBuf );
|
||||
// if ( !bOutputSpew )
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
{
|
||||
VPROF_BUDGET( "GCHost - EmitMessage", VPROF_BUDGETGROUP_STEAM );
|
||||
// !FIXME! DOTAMERGE
|
||||
//GGCInterface()->EmitSpew( pchGroupName, spewType, iSpewLevel, iLevelLog, pchBuf );
|
||||
GGCHost()->EmitMessage( pchGroupName, spewType, iSpewLevel, iLevelLog, pchBuf );
|
||||
}
|
||||
#else
|
||||
// TODO: Actually log it
|
||||
DevMsg( "%s", pchBuf );
|
||||
#endif
|
||||
}
|
||||
|
||||
//similar to the above, but takes in a group, and handles filtering messages out that are disabled, and extracting other info from the group
|
||||
void EmitBaseMessageV( const CGCEmitGroup& Group, SpewType_t spewType, int iConsoleLevel, int iLogLevel, const char *pchMsg, va_list vaArgs )
|
||||
{
|
||||
int iClampConsoleLevel = ( iConsoleLevel <= MIN( Group.GetConsoleLevel(), g_nMaxSpewLevel ) ) ? SPEW_ALWAYS : SPEW_NEVER;
|
||||
int iClampLogLevel = ( iLogLevel <= MIN( Group.GetLogLevel(), g_nMaxLogLevel ) ) ? LOG_ALWAYS : LOG_NEVER;
|
||||
if( ( iClampConsoleLevel != SPEW_NEVER ) || ( iClampLogLevel != LOG_NEVER ) )
|
||||
EmitBaseMessageV( Group.GetName(), spewType, iClampConsoleLevel, iClampLogLevel, pchMsg, vaArgs );
|
||||
}
|
||||
|
||||
//------------------------------
|
||||
// AssertError
|
||||
|
||||
void CGCEmitGroup::Internal_AssertError( const char *pchMsg, ... ) const
|
||||
{
|
||||
va_list args;
|
||||
va_start( args, pchMsg );
|
||||
AssertErrorV( pchMsg, args );
|
||||
va_end( args );
|
||||
}
|
||||
|
||||
void CGCEmitGroup::AssertErrorV( const char *pchMsg, va_list vaArgs ) const
|
||||
{
|
||||
EmitBaseMessageV( *this, SPEW_ASSERT, 1, 1, pchMsg, vaArgs );
|
||||
}
|
||||
|
||||
//------------------------------
|
||||
// Error
|
||||
|
||||
void CGCEmitGroup::Internal_Error( const char *pchMsg, ... ) const
|
||||
{
|
||||
va_list args;
|
||||
va_start( args, pchMsg );
|
||||
ErrorV( pchMsg, args );
|
||||
va_end( args );
|
||||
}
|
||||
|
||||
void CGCEmitGroup::ErrorV( const char *pchMsg, va_list vaArgs ) const
|
||||
{
|
||||
EmitBaseMessageV( *this, SPEW_ERROR, 1, 1, pchMsg, vaArgs );
|
||||
}
|
||||
|
||||
//------------------------------
|
||||
// Warning
|
||||
|
||||
void CGCEmitGroup::Internal_Warning( const char *pchMsg, ... ) const
|
||||
{
|
||||
va_list args;
|
||||
va_start( args, pchMsg );
|
||||
WarningV( pchMsg, args );
|
||||
va_end( args );
|
||||
}
|
||||
|
||||
void CGCEmitGroup::WarningV( const char *pchMsg, va_list vaArgs ) const
|
||||
{
|
||||
EmitBaseMessageV( *this, SPEW_WARNING, 2, 2, pchMsg, vaArgs );
|
||||
}
|
||||
|
||||
//------------------------------
|
||||
// Msg
|
||||
|
||||
void CGCEmitGroup::Internal_Msg( const char *pchMsg, ... ) const
|
||||
{
|
||||
va_list args;
|
||||
va_start( args, pchMsg );
|
||||
MsgV( pchMsg, args );
|
||||
va_end( args );
|
||||
}
|
||||
|
||||
void CGCEmitGroup::MsgV( const char *pchMsg, va_list vaArgs ) const
|
||||
{
|
||||
EmitBaseMessageV( *this, SPEW_MESSAGE, 3, 3, pchMsg, vaArgs );
|
||||
}
|
||||
|
||||
//------------------------------
|
||||
// Verbose
|
||||
|
||||
void CGCEmitGroup::Internal_Verbose( const char *pchMsg, ... ) const
|
||||
{
|
||||
va_list args;
|
||||
va_start( args, pchMsg );
|
||||
VerboseV( pchMsg, args );
|
||||
va_end( args );
|
||||
}
|
||||
|
||||
void CGCEmitGroup::VerboseV( const char *pchMsg, va_list vaArgs ) const
|
||||
{
|
||||
EmitBaseMessageV( *this, SPEW_MESSAGE, 4, 4, pchMsg, vaArgs );
|
||||
}
|
||||
|
||||
|
||||
//------------------------------
|
||||
// Verbose
|
||||
|
||||
void CGCEmitGroup::Internal_BoldMsg( const char *pchMsg, ... ) const
|
||||
{
|
||||
va_list args;
|
||||
va_start( args, pchMsg );
|
||||
BoldMsgV( pchMsg, args );
|
||||
va_end( args );
|
||||
}
|
||||
|
||||
void CGCEmitGroup::BoldMsgV( const char *pchMsg, va_list vaArgs ) const
|
||||
{
|
||||
// !FIXME! DOTAMERGE
|
||||
//EmitBaseMessageV( *this, SPEW_BOLD_MESSAGE, 1, 1, pchMsg, vaArgs );
|
||||
EmitBaseMessageV( *this, SPEW_MESSAGE, 1, 1, pchMsg, vaArgs );
|
||||
}
|
||||
|
||||
|
||||
//------------------------------
|
||||
// General Emit
|
||||
|
||||
|
||||
void CGCEmitGroup::Internal_Emit( EMsgLevel eLvl, PRINTF_FORMAT_STRING const char *pchMsg, ... ) const
|
||||
{
|
||||
va_list args;
|
||||
va_start( args, pchMsg );
|
||||
EmitV( eLvl, pchMsg, args );
|
||||
va_end( args );
|
||||
}
|
||||
|
||||
void CGCEmitGroup::EmitV( EMsgLevel eLvl, PRINTF_FORMAT_STRING const char *pchMsg, va_list vaArgs ) const
|
||||
{
|
||||
switch( eLvl )
|
||||
{
|
||||
case kMsg_Error: ErrorV( pchMsg, vaArgs ); break;
|
||||
case kMsg_Warning: WarningV( pchMsg, vaArgs ); break;
|
||||
case kMsg_Msg: MsgV( pchMsg, vaArgs ); break;
|
||||
case kMsg_Verbose: VerboseV( pchMsg, vaArgs ); break;
|
||||
default:
|
||||
AssertMsg1( false, "Unexpected error level of %d provided to GCEmitGroup::Emit", eLvl );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Legacy Interface
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
void EGInternal_EmitInfo( const CGCEmitGroup& Group, int iLevel, int iLevelLog, const char *pchMsg, ... )
|
||||
{
|
||||
va_list args;
|
||||
va_start( args, pchMsg );
|
||||
EmitBaseMessageV( Group, SPEW_MESSAGE, iLevel, iLevelLog, pchMsg, args );
|
||||
va_end( args );
|
||||
}
|
||||
|
||||
void EmitInfoV( const CGCEmitGroup& Group, int iLevel, int iLevelLog, const char *pchMsg, va_list vaArgs )
|
||||
{
|
||||
EmitBaseMessageV( Group, SPEW_MESSAGE, iLevel, iLevelLog, pchMsg, vaArgs );
|
||||
}
|
||||
|
||||
void EmitWarning( const CGCEmitGroup& Group, int iLevel, const char *pchMsg, ... )
|
||||
{
|
||||
va_list args;
|
||||
va_start( args, pchMsg );
|
||||
EmitBaseMessageV( Group, SPEW_WARNING, iLevel, iLevel, pchMsg, args );
|
||||
va_end( args );
|
||||
}
|
||||
|
||||
void EmitError( const CGCEmitGroup& Group, const char *pchMsg, ... )
|
||||
{
|
||||
va_list args;
|
||||
va_start( args, pchMsg );
|
||||
EmitBaseMessageV( Group, SPEW_ERROR, 1, 1, pchMsg, args );
|
||||
va_end( args );
|
||||
}
|
||||
|
||||
// Emit an assert-like error, generating a minidump
|
||||
void EmitAssertError( const CGCEmitGroup& Group, const char *pchMsg, ... )
|
||||
{
|
||||
va_list args;
|
||||
va_start( args, pchMsg );
|
||||
EmitBaseMessageV( Group, SPEW_ASSERT, 1, 1, pchMsg, args );
|
||||
va_end( args );
|
||||
}
|
||||
|
||||
//legacy group types
|
||||
DECLARE_GC_EMIT_GROUP( SPEW_SYSTEM_MISC, system );
|
||||
DECLARE_GC_EMIT_GROUP( SPEW_JOB, job );
|
||||
DECLARE_GC_EMIT_GROUP( SPEW_CONSOLE, console );
|
||||
DECLARE_GC_EMIT_GROUP( SPEW_GC, gc );
|
||||
DECLARE_GC_EMIT_GROUP( SPEW_SQL, sql );
|
||||
DECLARE_GC_EMIT_GROUP( SPEW_NETWORK, network );
|
||||
DECLARE_GC_EMIT_GROUP( SPEW_SHAREDOBJ, sharedobj );
|
||||
DECLARE_GC_EMIT_GROUP( SPEW_MICROTXN, microtxn );
|
||||
DECLARE_GC_EMIT_GROUP( SPEW_PROMO, promo );
|
||||
DECLARE_GC_EMIT_GROUP( SPEW_PKGITEM, pkgitem );
|
||||
DECLARE_GC_EMIT_GROUP( SPEW_ECONOMY, econ );
|
||||
DECLARE_GC_EMIT_GROUP( SPEW_THREADS, threads );
|
||||
|
||||
} // namespace GCSDK
|
||||
@@ -0,0 +1,94 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Holds function bodies for GCSDK
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "gcmsg.h"
|
||||
#include "msgprotobuf.h"
|
||||
|
||||
#include "tier0/memdbgoff.h"
|
||||
|
||||
#ifdef GC
|
||||
namespace GCSDK
|
||||
{
|
||||
IMPLEMENT_CLASS_MEMPOOL( CStructNetPacket, 1000, UTLMEMORYPOOL_GROW_SLOW );
|
||||
}
|
||||
#endif
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
CUtlString GCMsgHdr_t::GetHeaderDescription( )
|
||||
{
|
||||
CUtlString desc;
|
||||
desc.Format( "gc msg %s, SteamID %llu", PchMsgNameFromEMsg( m_eMsg ), m_ulSteamID );
|
||||
return desc;
|
||||
}
|
||||
|
||||
CUtlString GCMsgHdrEx_t::GetHeaderDescription( )
|
||||
{
|
||||
CUtlString desc;
|
||||
desc.Format( "gc msg %s, SteamID %llu, version %hd, job source %llu, job target %llu", PchMsgNameFromEMsg( m_eMsg ), m_ulSteamID, m_nHdrVersion, m_JobIDSource, m_JobIDTarget );
|
||||
return desc;
|
||||
}
|
||||
|
||||
CUtlString GCMsgInterHdr_t::GetHeaderDescription( )
|
||||
{
|
||||
CUtlString desc;
|
||||
desc.Format( "gc inter msg %s, SourceAppID %d, version %hd, job source %llu, job target %llu", PchMsgNameFromEMsg( m_eMsg ), m_unSourceAppId, m_nHdrVersion, m_JobIDSource, m_JobIDTarget );
|
||||
return desc;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Takes a pNetPacket, then converts it to the appropriate actual message
|
||||
// type and return the IMsgNetPacket interface.
|
||||
//-----------------------------------------------------------------------------
|
||||
IMsgNetPacket *IMsgNetPacketFromCNetPacket( CNetPacket *pNetPacket )
|
||||
{
|
||||
uint32 cubPkt = pNetPacket->CubData();
|
||||
uint8* pubPkt = pNetPacket->PubData();
|
||||
|
||||
if ( cubPkt >= sizeof( ProtoBufMsgHeader_t ) && ( ( ( ProtoBufMsgHeader_t *) pubPkt )->m_EMsgFlagged & k_EMsgProtoBufFlag ) )
|
||||
{
|
||||
CProtoBufNetPacket *pMsgNetPacket = new CProtoBufNetPacket( pNetPacket, GCProtoBufMsgSrc_Unspecified, CSteamID(), 0, 0x7FFFFFFF & ( ( ProtoBufMsgHeader_t *) pubPkt )->m_EMsgFlagged );
|
||||
if ( !pMsgNetPacket->IsValid() )
|
||||
{
|
||||
pMsgNetPacket->Release();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return pMsgNetPacket;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( cubPkt >= sizeof( GCMsgHdrEx_t ) )
|
||||
{
|
||||
CStructNetPacket *pMsgNetPacket = (CStructNetPacket*)malloc( sizeof( CStructNetPacket ) );
|
||||
Construct( pMsgNetPacket, pNetPacket );
|
||||
return pMsgNetPacket;
|
||||
}
|
||||
}
|
||||
|
||||
EmitError( SPEW_GC, "IMsgNetPacketFromCNetPacket: malformed packet, size %d bytes\n", cubPkt );
|
||||
for ( uint32 i = 0 ; i < cubPkt ; i += 16 )
|
||||
{
|
||||
char buf[512];
|
||||
char *d = buf;
|
||||
d += Q_snprintf(d, 12, "%08X:", i );
|
||||
for ( uint32 j = i ; j < i+16 && j < cubPkt ; ++j )
|
||||
{
|
||||
d += Q_snprintf(d, 8, " %02X", pubPkt[j] );
|
||||
}
|
||||
*d = '\0';
|
||||
EmitError( SPEW_GC, "%s\n", buf );
|
||||
}
|
||||
|
||||
AssertMsg( false, "IMsgNetPacketFromCNetPacket couldn't detect any appropriate msg format, returning NULL." );
|
||||
return NULL;
|
||||
}
|
||||
} // namespace GCSDK
|
||||
@@ -0,0 +1,119 @@
|
||||
//====== Copyright (c), Valve Corporation, All rights reserved. =======
|
||||
//
|
||||
// Purpose: Implements parallel job farming process
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rtime.h"
|
||||
#include "gcparalleljobfarm.h"
|
||||
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
bool IYieldingParallelFarmJobHandler::BYieldingExecuteParallel( int numJobsParallel, char const *pchJobName, uint nTimeoutSec )
|
||||
{
|
||||
AssertRunningJob();
|
||||
|
||||
if ( !pchJobName )
|
||||
pchJobName = GJobCur().GetName();
|
||||
|
||||
struct CParallelFarmHeapData_t
|
||||
{
|
||||
explicit CParallelFarmHeapData_t( IYieldingParallelFarmJobHandler *pHandler, int numJobsFarmLimit )
|
||||
{
|
||||
m_pHandler = pHandler;
|
||||
m_jobIdParent = GJobCur().GetJobID();
|
||||
m_numJobsFarmed = 0;
|
||||
m_numJobsFarmLimit = MAX( 1, numJobsFarmLimit );
|
||||
m_iJobSequenceCounter = 0;
|
||||
m_bErrorEncountered = false;
|
||||
m_bWorkloadCompleted = false;
|
||||
}
|
||||
|
||||
IYieldingParallelFarmJobHandler *m_pHandler;
|
||||
JobID_t m_jobIdParent;
|
||||
int m_numJobsFarmLimit;
|
||||
int m_numJobsFarmed;
|
||||
int m_iJobSequenceCounter;
|
||||
bool m_bErrorEncountered;
|
||||
bool m_bWorkloadCompleted;
|
||||
};
|
||||
CParallelFarmHeapData_t *pHeapData = new CParallelFarmHeapData_t( this, numJobsParallel );
|
||||
|
||||
class CYieldingParallelFarmJob : public CGCJob
|
||||
{
|
||||
public:
|
||||
CYieldingParallelFarmJob( CGCBase *pGC, CParallelFarmHeapData_t *pJobData, char const *pchJobName, uint nTimeoutSec ) : CGCJob( pGC, pchJobName )
|
||||
, m_pJobData( pJobData ), m_iJobSequenceCounter( pJobData->m_iJobSequenceCounter ), m_nTimeoutSec( nTimeoutSec )
|
||||
{
|
||||
}
|
||||
virtual bool BYieldingRunJob( void *pvStartParam )
|
||||
{
|
||||
if ( m_nTimeoutSec )
|
||||
SetJobTimeout( m_nTimeoutSec );
|
||||
|
||||
bool bWorkloadCompleted = false;
|
||||
bool bResult = m_pJobData->m_pHandler
|
||||
? m_pJobData->m_pHandler->BYieldingRunWorkload( m_iJobSequenceCounter, &bWorkloadCompleted )
|
||||
: false;
|
||||
|
||||
if ( !bResult )
|
||||
m_pJobData->m_bErrorEncountered = true;
|
||||
else if ( bWorkloadCompleted )
|
||||
m_pJobData->m_bWorkloadCompleted = true;
|
||||
|
||||
-- m_pJobData->m_numJobsFarmed;
|
||||
|
||||
if ( !m_pJobData->m_bErrorEncountered && !m_pJobData->m_bWorkloadCompleted )
|
||||
{
|
||||
CYieldingParallelFarmJob *pFarmedJob = new CYieldingParallelFarmJob( m_pGC, m_pJobData, GetName(), m_nTimeoutSec );
|
||||
++ m_pJobData->m_numJobsFarmed;
|
||||
++ m_pJobData->m_iJobSequenceCounter;
|
||||
pFarmedJob->StartJobDelayed( NULL );
|
||||
}
|
||||
|
||||
if ( !m_pJobData->m_numJobsFarmed )
|
||||
{ // No more farmed jobs to wait for
|
||||
m_pGC->GetJobMgr().BRouteWorkItemCompletedDelayed( m_pJobData->m_jobIdParent, false );
|
||||
}
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
protected:
|
||||
CParallelFarmHeapData_t *m_pJobData;
|
||||
int m_iJobSequenceCounter;
|
||||
uint m_nTimeoutSec;
|
||||
};
|
||||
|
||||
for ( ; ; ++ pHeapData->m_iJobSequenceCounter )
|
||||
{
|
||||
if ( pHeapData->m_numJobsFarmed < pHeapData->m_numJobsFarmLimit )
|
||||
{
|
||||
CYieldingParallelFarmJob *pFarmedJob = new CYieldingParallelFarmJob( GGCBase(), pHeapData, pchJobName, nTimeoutSec );
|
||||
++ pHeapData->m_numJobsFarmed;
|
||||
pFarmedJob->StartJobDelayed( NULL );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !GJobCur().BYieldingWaitForWorkItem( pchJobName ) )
|
||||
{
|
||||
EmitError( SPEW_GC, "YieldingExecuteParallel: failed to sync with %u farmed work items.\n", pHeapData->m_numJobsFarmed );
|
||||
pHeapData->m_bErrorEncountered = true;
|
||||
pHeapData->m_pHandler = NULL; // handler itself may become invalid when the function returns
|
||||
return false; // leak pHeapData because work items might still be running and this can avoid a crash (this condition is abnormal)
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool bResult = pHeapData->m_bWorkloadCompleted && !pHeapData->m_bErrorEncountered;
|
||||
delete pHeapData;
|
||||
return bResult;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,489 @@
|
||||
//========= Copyright (c), Valve LLC, All rights reserved. ============
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "gcreportprinter.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
|
||||
CGCReportPrinter::Variant_t::Variant_t() :
|
||||
m_nInt( 0 ),
|
||||
m_fFloat( 0 )
|
||||
{}
|
||||
|
||||
CGCReportPrinter::CGCReportPrinter()
|
||||
{
|
||||
}
|
||||
|
||||
CGCReportPrinter::~CGCReportPrinter()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
bool CGCReportPrinter::AddStringColumn( const char* pszColumn )
|
||||
{
|
||||
//don't allow adding columns if data is already present
|
||||
if( m_Rows.Count() > 0 )
|
||||
return false;
|
||||
|
||||
int nIndex = m_Columns.AddToTail();
|
||||
Column_t& col = m_Columns[ nIndex ];
|
||||
col.m_sName = pszColumn;
|
||||
col.m_eType = eCol_String;
|
||||
col.m_eSummary = eSummary_None;
|
||||
col.m_nNumDecimals = 0;
|
||||
col.m_eIntDisplay = eIntDisplay_Normal;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGCReportPrinter::AddIntColumn( const char* pszColumn, ESummaryType eSummary, EIntDisplayType eIntDisplay /* = eIntDisplay_Normal */ )
|
||||
{
|
||||
//don't allow adding columns if data is already present
|
||||
if( m_Rows.Count() > 0 )
|
||||
return false;
|
||||
|
||||
int nIndex = m_Columns.AddToTail();
|
||||
Column_t& col = m_Columns[ nIndex ];
|
||||
col.m_sName = pszColumn;
|
||||
col.m_eType = eCol_Int;
|
||||
col.m_eSummary = eSummary;
|
||||
col.m_nNumDecimals = 0;
|
||||
col.m_eIntDisplay = eIntDisplay;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGCReportPrinter::AddFloatColumn( const char* pszColumn, ESummaryType eSummary, uint32 unNumDecimal /* = 2 */ )
|
||||
{
|
||||
//don't allow adding columns if data is already present
|
||||
if( m_Rows.Count() > 0 )
|
||||
return false;
|
||||
|
||||
int nIndex = m_Columns.AddToTail();
|
||||
Column_t& col = m_Columns[ nIndex ];
|
||||
col.m_sName = pszColumn;
|
||||
col.m_eType = eCol_Float;
|
||||
col.m_eSummary = eSummary;
|
||||
col.m_nNumDecimals = unNumDecimal;
|
||||
col.m_eIntDisplay = eIntDisplay_Normal;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGCReportPrinter::AddSteamIDColumn( const char* pszColumn )
|
||||
{
|
||||
//don't allow adding columns if data is already present
|
||||
if( m_Rows.Count() > 0 )
|
||||
return false;
|
||||
|
||||
int nIndex = m_Columns.AddToTail();
|
||||
Column_t& col = m_Columns[ nIndex ];
|
||||
col.m_sName = pszColumn;
|
||||
col.m_eType = eCol_SteamID;
|
||||
col.m_eSummary = eSummary_None;
|
||||
col.m_nNumDecimals = 0;
|
||||
col.m_eIntDisplay = eIntDisplay_Normal;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGCReportPrinter::ClearData()
|
||||
{
|
||||
m_Rows.PurgeAndDeleteElements();
|
||||
}
|
||||
|
||||
//called to reset the entire report
|
||||
void CGCReportPrinter::Clear()
|
||||
{
|
||||
ClearData();
|
||||
m_Columns.Purge();
|
||||
}
|
||||
|
||||
//called to commit the values that have been added as a new row
|
||||
bool CGCReportPrinter::CommitRow()
|
||||
{
|
||||
//only let full rows be committed
|
||||
if( m_RowBuilder.Count() != m_Columns.Count() )
|
||||
return false;
|
||||
if( m_Columns.IsEmpty() )
|
||||
return false;
|
||||
|
||||
m_Rows.AddToTail( new TRow( m_RowBuilder ) );
|
||||
m_RowBuilder.RemoveAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
//called to add the various data to the report, the order of this must match the columns that were added originally
|
||||
bool CGCReportPrinter::StrValue( const char* pszStr, const char* pszLink )
|
||||
{
|
||||
//make sure we have a following column and that the type matches
|
||||
if( ( m_RowBuilder.Count() >= m_Columns.Count() ) || ( m_Columns[ m_RowBuilder.Count() ].m_eType != eCol_String ) )
|
||||
return false;
|
||||
|
||||
Variant_t& val = m_RowBuilder[ m_RowBuilder.AddToTail() ];
|
||||
val.m_sStr = pszStr;
|
||||
val.m_sLink = pszLink;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGCReportPrinter::IntValue( int64 nValue, const char* pszLink )
|
||||
{
|
||||
//make sure we have a following column and that the type matches
|
||||
if( ( m_RowBuilder.Count() >= m_Columns.Count() ) || ( m_Columns[ m_RowBuilder.Count() ].m_eType != eCol_Int ) )
|
||||
return false;
|
||||
|
||||
Variant_t& val = m_RowBuilder[ m_RowBuilder.AddToTail() ];
|
||||
val.m_nInt = nValue;
|
||||
val.m_sLink = pszLink;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGCReportPrinter::FloatValue( double fValue, const char* pszLink )
|
||||
{
|
||||
//make sure we have a following column and that the type matches
|
||||
if( ( m_RowBuilder.Count() >= m_Columns.Count() ) || ( m_Columns[ m_RowBuilder.Count() ].m_eType != eCol_Float ) )
|
||||
return false;
|
||||
|
||||
Variant_t& val = m_RowBuilder[ m_RowBuilder.AddToTail() ];
|
||||
val.m_fFloat = fValue;
|
||||
val.m_sLink = pszLink;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGCReportPrinter::SteamIDValue( CSteamID id, const char* pszLink )
|
||||
{
|
||||
//make sure we have a following column and that the type matches
|
||||
if( ( m_RowBuilder.Count() >= m_Columns.Count() ) || ( m_Columns[ m_RowBuilder.Count() ].m_eType != eCol_SteamID ) )
|
||||
return false;
|
||||
|
||||
Variant_t& val = m_RowBuilder[ m_RowBuilder.AddToTail() ];
|
||||
val.m_SteamID = id;
|
||||
val.m_sLink = pszLink;
|
||||
return true;
|
||||
}
|
||||
|
||||
//class that implements sorting our rows based upon a provided column with ascending or descending ordering
|
||||
class CReportRowSorter
|
||||
{
|
||||
public:
|
||||
|
||||
CReportRowSorter( bool bDescending, uint32 nCol, CGCReportPrinter::EColumnType eType ) :
|
||||
m_bDescending( bDescending ), m_nCol( nCol ), m_eType( eType )
|
||||
{
|
||||
}
|
||||
|
||||
bool operator()( const CGCReportPrinter::TRow* pR1, const CGCReportPrinter::TRow* pR2 )
|
||||
{
|
||||
//to implement ascending vs descending, we can just flip our inputs
|
||||
if( m_bDescending )
|
||||
std::swap( pR1, pR2 );
|
||||
|
||||
const CGCReportPrinter::Variant_t& v1 = ( *pR1 )[ m_nCol ];
|
||||
const CGCReportPrinter::Variant_t& v2 = ( *pR2 )[ m_nCol ];
|
||||
|
||||
switch( m_eType )
|
||||
{
|
||||
case CGCReportPrinter::eCol_String:
|
||||
return stricmp( v1.m_sStr, v2.m_sStr ) < 0;
|
||||
case CGCReportPrinter::eCol_Int:
|
||||
return v1.m_nInt < v2.m_nInt;
|
||||
case CGCReportPrinter::eCol_Float:
|
||||
return v1.m_fFloat < v2.m_fFloat;
|
||||
case CGCReportPrinter::eCol_SteamID:
|
||||
return v1.m_SteamID < v2.m_SteamID;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool m_bDescending;
|
||||
uint32 m_nCol;
|
||||
CGCReportPrinter::EColumnType m_eType;
|
||||
};
|
||||
|
||||
|
||||
//sorts the report based upon the specified column name
|
||||
void CGCReportPrinter::SortReport( const char* pszColumn, bool bDescending )
|
||||
{
|
||||
//find our column
|
||||
FOR_EACH_VEC( m_Columns, nCol )
|
||||
{
|
||||
if( stricmp( m_Columns[ nCol ].m_sName, pszColumn ) == 0 )
|
||||
{
|
||||
CReportRowSorter sorter( bDescending, nCol, m_Columns[ nCol ].m_eType );
|
||||
std::sort( m_Rows.begin(), m_Rows.end(), sorter );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CGCReportPrinter::SortReport( uint32 nColIndex, bool bDescending )
|
||||
{
|
||||
if( nColIndex < ( uint32 )m_Columns.Count() )
|
||||
{
|
||||
CReportRowSorter sorter( bDescending, nColIndex, m_Columns[ nColIndex ].m_eType );
|
||||
std::sort( m_Rows.begin(), m_Rows.end(), sorter );
|
||||
}
|
||||
}
|
||||
|
||||
//utility to count the number of digits on the provided integer
|
||||
static uint CountDigits( int64 nInt )
|
||||
{
|
||||
//the zero special case, since it would otherwise fall out of the loop too early
|
||||
if( nInt == 0 )
|
||||
return 1;
|
||||
|
||||
int nDigits = 0;
|
||||
if( nInt < 0 )
|
||||
{
|
||||
//for the minus sign
|
||||
nDigits++;
|
||||
}
|
||||
|
||||
while( nInt != 0 )
|
||||
{
|
||||
nInt /= 10;
|
||||
nDigits++;
|
||||
}
|
||||
|
||||
return nDigits;
|
||||
}
|
||||
|
||||
static uint CountIntWidth( int64 nValue, CGCReportPrinter::EIntDisplayType eIntDisplay )
|
||||
{
|
||||
uint unDigits;
|
||||
switch ( eIntDisplay )
|
||||
{
|
||||
case CGCReportPrinter::eIntDisplay_Memory_MB:
|
||||
// "1234.56 MB"
|
||||
unDigits = CountDigits( nValue / k_nMegabyte ) + 1 + 2 + 3;
|
||||
break;
|
||||
|
||||
case CGCReportPrinter::eIntDisplay_Normal:
|
||||
default:
|
||||
// 12345678
|
||||
unDigits = CountDigits( nValue );
|
||||
break;
|
||||
}
|
||||
return unDigits;
|
||||
}
|
||||
|
||||
static const char * GetIntValueDisplay( int64 nValue, CGCReportPrinter::EIntDisplayType eIntDisplay )
|
||||
{
|
||||
static CFmtStr1024 s_fmtResult;
|
||||
switch ( eIntDisplay )
|
||||
{
|
||||
case CGCReportPrinter::eIntDisplay_Memory_MB:
|
||||
// "1234.56 MB"
|
||||
s_fmtResult.sprintf( "%lld.%02u MB", ( nValue / k_nMegabyte ), (uint32)( 100.0f * ( ( abs( nValue ) % k_nMegabyte ) / (float)k_nMegabyte ) ) );
|
||||
break;
|
||||
|
||||
case CGCReportPrinter::eIntDisplay_Normal:
|
||||
default:
|
||||
// 12345678
|
||||
s_fmtResult.sprintf( "%lld", nValue );
|
||||
break;
|
||||
}
|
||||
return s_fmtResult;
|
||||
}
|
||||
|
||||
//called to print out the provided report
|
||||
void CGCReportPrinter::PrintReport( CGCEmitGroup& eg, uint32 nTop )
|
||||
{
|
||||
//we need to determine our totals and maximum row widths for our columns first
|
||||
CUtlVector< uint32 > vColWidths;
|
||||
vColWidths.EnsureCapacity( m_Columns.Count() );
|
||||
CUtlVector< Variant_t > vSummary;
|
||||
vSummary.EnsureCapacity( m_Columns.Count() );
|
||||
|
||||
CFmtStr1024 vMsg;
|
||||
CFmtStr1024 vSeparator;
|
||||
|
||||
FOR_EACH_VEC( m_Columns, nCol )
|
||||
{
|
||||
const Column_t& col = m_Columns[ nCol ];
|
||||
uint32 nColWidth = V_strlen( col.m_sName );
|
||||
Variant_t summary;
|
||||
|
||||
//run through all the values to find the row widths and the summary values
|
||||
FOR_EACH_VEC( m_Rows, nRow )
|
||||
{
|
||||
//bail after the first N elements
|
||||
if( ( nTop > 0 ) && ( ( uint32 )nRow >= nTop ) )
|
||||
break;
|
||||
|
||||
const Variant_t& v = ( *m_Rows[ nRow ] )[ nCol ];
|
||||
switch( col.m_eType )
|
||||
{
|
||||
case eCol_String:
|
||||
nColWidth = MAX( nColWidth, strlen( v.m_sStr ) );
|
||||
break;
|
||||
case eCol_SteamID:
|
||||
nColWidth = MAX( nColWidth, strlen( v.m_SteamID.Render() ) );
|
||||
break;
|
||||
case eCol_Float:
|
||||
nColWidth = MAX( nColWidth, CountDigits( ( int64 )v.m_fFloat ) + 1 + col.m_nNumDecimals );
|
||||
switch( col.m_eSummary )
|
||||
{
|
||||
case eSummary_Max:
|
||||
summary.m_fFloat = MAX( summary.m_fFloat, v.m_fFloat );
|
||||
break;
|
||||
case eSummary_Total:
|
||||
summary.m_fFloat += v.m_fFloat;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case eCol_Int:
|
||||
nColWidth = MAX( nColWidth, CountIntWidth( v.m_nInt, col.m_eIntDisplay ) );
|
||||
switch( col.m_eSummary )
|
||||
{
|
||||
case eSummary_Max:
|
||||
summary.m_nInt = MAX( summary.m_nInt, v.m_nInt );
|
||||
break;
|
||||
case eSummary_Total:
|
||||
summary.m_nInt += v.m_nInt;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//make sure the summary value contributes to the column width
|
||||
switch( col.m_eType )
|
||||
{
|
||||
case eCol_Float:
|
||||
nColWidth = MAX( nColWidth, CountDigits( ( int64 )summary.m_fFloat ) + 1 + col.m_nNumDecimals );
|
||||
break;
|
||||
case eCol_Int:
|
||||
nColWidth = MAX( nColWidth, CountIntWidth( summary.m_nInt, col.m_eIntDisplay ) );
|
||||
break;
|
||||
}
|
||||
|
||||
//initialize our column sizes
|
||||
vColWidths.AddToTail( nColWidth );
|
||||
vSummary.AddToTail( summary );
|
||||
|
||||
vMsg.AppendFormat( "%*s", nColWidth, col.m_sName.String() );
|
||||
vMsg.Append( ' ' );
|
||||
|
||||
for( uint32 nChar = 0; nChar < nColWidth; nChar++ )
|
||||
vSeparator.Append( '-' );
|
||||
vSeparator.Append( ' ' );
|
||||
}
|
||||
|
||||
//now print our header
|
||||
vMsg.Append( '\n' );
|
||||
vSeparator.Append( '\n' );
|
||||
|
||||
EG_MSG( eg, "%s", vMsg.String() );
|
||||
EG_MSG( eg, "%s", vSeparator.String() );
|
||||
|
||||
//buffer for compositing our value
|
||||
CFmtStr1024 vValue;
|
||||
|
||||
//now print each of our columns
|
||||
FOR_EACH_VEC( m_Rows, nRow )
|
||||
{
|
||||
//bail after the first N elements
|
||||
if( ( nTop > 0 ) && ( ( uint32 )nRow >= nTop ) )
|
||||
break;
|
||||
|
||||
vMsg.Clear();
|
||||
FOR_EACH_VEC( m_Columns, nCol )
|
||||
{
|
||||
const Column_t& col = m_Columns[ nCol ];
|
||||
const Variant_t& v = ( *m_Rows[ nRow ] )[ nCol ];
|
||||
const uint32 nColWidth = vColWidths[ nCol ];
|
||||
|
||||
vValue.Clear();
|
||||
switch( col.m_eType )
|
||||
{
|
||||
case eCol_String:
|
||||
vValue.Append( v.m_sStr.String() );
|
||||
break;
|
||||
case eCol_SteamID:
|
||||
vValue.Append( v.m_SteamID.Render() );
|
||||
break;
|
||||
case eCol_Float:
|
||||
vValue.sprintf( "%.*f", col.m_nNumDecimals, v.m_fFloat );
|
||||
break;
|
||||
case eCol_Int:
|
||||
vValue.Append( GetIntValueDisplay( v.m_nInt, col.m_eIntDisplay ) );
|
||||
break;
|
||||
}
|
||||
|
||||
//print out spaces before we do the link (so we don't have the whole table underlined)
|
||||
uint32 nValueLen = vValue.Length();
|
||||
uint32 nNumSpaces = nColWidth - MIN( nColWidth, nValueLen );
|
||||
for( uint32 nCurrSpace = 0; nCurrSpace < nNumSpaces; nCurrSpace++ )
|
||||
vMsg.Append( ' ' );
|
||||
|
||||
//print out the link if one is provided
|
||||
if( !v.m_sLink.IsEmpty() )
|
||||
{
|
||||
vMsg.AppendFormat( "<link cmd=\"%s\">", v.m_sLink.String() );
|
||||
vMsg.Append( vValue );
|
||||
vMsg.Append( "</link>" );
|
||||
}
|
||||
else
|
||||
{
|
||||
//allow for steam ID special linking if no link is specified
|
||||
if( col.m_eType == eCol_SteamID )
|
||||
{
|
||||
// !FIXME! DOTAMERGE
|
||||
//vMsg.Append( v.m_SteamID.RenderLink() );
|
||||
vMsg.Append( v.m_SteamID.Render() );
|
||||
} else
|
||||
vMsg.Append( vValue );
|
||||
}
|
||||
|
||||
vMsg.Append( ' ' );
|
||||
}
|
||||
|
||||
vMsg.Append( '\n' );
|
||||
EG_MSG( eg, "%s", vMsg.String() );
|
||||
}
|
||||
|
||||
//and finally our footer
|
||||
EG_MSG( eg, "%s", vSeparator.String() );
|
||||
|
||||
//and our summary
|
||||
{
|
||||
vMsg.Clear();
|
||||
FOR_EACH_VEC( m_Columns, nCol )
|
||||
{
|
||||
const Column_t& col = m_Columns[ nCol ];
|
||||
const Variant_t& v = vSummary[ nCol ];
|
||||
const uint32 nColWidth = vColWidths[ nCol ];
|
||||
|
||||
if( ( col.m_eType == eCol_String ) || ( col.m_eSummary == eSummary_None ) )
|
||||
{
|
||||
vMsg.AppendFormat( "%*s ", nColWidth, "" );
|
||||
}
|
||||
else
|
||||
{
|
||||
switch( col.m_eType )
|
||||
{
|
||||
case eCol_Float:
|
||||
vMsg.AppendFormat( "%*.*f ", nColWidth, col.m_nNumDecimals, v.m_fFloat );
|
||||
break;
|
||||
case eCol_Int:
|
||||
vMsg.AppendFormat( "%*s ", nColWidth, GetIntValueDisplay( v.m_nInt, col.m_eIntDisplay ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vMsg.Append( '\n' );
|
||||
EG_MSG( eg, "%s", vMsg.String() );
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace GCSDK
|
||||
@@ -0,0 +1,160 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// GCSDK.VPC
|
||||
//
|
||||
// Project Script for the Game Coordinator SDK
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
$macro SRCDIR ".."
|
||||
$Macro GENERATED_PROTO_DIR "$SRCDIR\gcsdk\generated_proto"
|
||||
|
||||
$include "$SRCDIR\vpc_scripts\source_lib_base.vpc"
|
||||
$include "$SRCDIR\vpc_scripts\protobuf_builder.vpc"
|
||||
$include "$SRCDIR\gcsdk\steammessages_include.vpc"
|
||||
$include "$SRCDIR\gcsdk\gcsdk_gcmessages_include.vpc"
|
||||
|
||||
$Configuration
|
||||
{
|
||||
$Compiler
|
||||
{
|
||||
//$PreprocessorDefinitions "$BASE;STEAM"
|
||||
$AdditionalIncludeDirectories "$BASE;$SRCDIR\gcsdk\steamextra;..;..\public;..\public\gcsdk;$SRCDIR\thirdparty\JSON_parser;."
|
||||
$PreprocessorDefinitions "$BASE;TF" // Some steamextra files have tweaks unfortunately
|
||||
$Create/UsePrecompiledHeader "Use Precompiled Header (/Yu)"
|
||||
$AdditionalOptions "$BASE /YlGCSDK_PCH_Symbol"
|
||||
}
|
||||
}
|
||||
|
||||
$Project "gcsdk"
|
||||
{
|
||||
$Folder "Source Files"
|
||||
{
|
||||
$File "stdafx.cpp"
|
||||
{
|
||||
$Configuration
|
||||
{
|
||||
$Compiler
|
||||
{
|
||||
$Create/UsePrecompiledHeader "Create Precompiled Header (/Yc)"
|
||||
|
||||
// Generate a bogus symbol to fix the PCH "no debug symbols" warning
|
||||
//$AdditionalOptions "$BASE /YlBogusGCSDKSymbol"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// turn off the debug check for the debug build
|
||||
$File "$SRCDIR\common\debug_lib_check.cpp"
|
||||
{
|
||||
$Configuration "Debug"
|
||||
{
|
||||
$ExcludedFromBuild "Yes"
|
||||
}
|
||||
}
|
||||
|
||||
$File "gcclient.cpp"
|
||||
$File "gcconstants.cpp"
|
||||
$File "gclogger.cpp"
|
||||
$File "gcmsg.cpp"
|
||||
$File "job.cpp"
|
||||
$File "jobmgr.cpp"
|
||||
$File "jobtime.cpp"
|
||||
$File "messagelist.cpp"
|
||||
$File "msgprotobuf.cpp"
|
||||
$File "netpacket.cpp"
|
||||
$File "netpacketpool.cpp"
|
||||
$File "protobufsharedobject.cpp"
|
||||
$File "sharedobject.cpp"
|
||||
$File "sharedobjectcache.cpp"
|
||||
$File "gcclient_sharedobjectcache.cpp"
|
||||
$File "steamextra\misc.cpp"
|
||||
$File "steamextra\rtime.cpp"
|
||||
$File "steamextra\steamid.cpp"
|
||||
{
|
||||
// Needs to #define something prior to including the PCH
|
||||
$Configuration
|
||||
{
|
||||
$Compiler
|
||||
{
|
||||
$Create/UsePrecompiledHeader "Not Using Precompiled Headers"
|
||||
}
|
||||
}
|
||||
}
|
||||
$File "steamextra\tier1\hashglobals.cpp"
|
||||
$File "steamextra\tier1\tsmempool.cpp"
|
||||
$File "steamextra\tier1\tsmultimempool.cpp"
|
||||
$File "workthreadpool.cpp"
|
||||
$File "webapi_response.cpp"
|
||||
$File "$SRCDIR\thirdparty\JSON_parser\JSON_parser.c"
|
||||
{
|
||||
$Configuration
|
||||
{
|
||||
$Compiler
|
||||
{
|
||||
$Create/UsePrecompiledHeader "Not Using Precompiled Headers"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$Folder "Header Files"
|
||||
{
|
||||
$File "stdafx.h"
|
||||
|
||||
$File "$SRCDIR\public\gcsdk\enumutils.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcclient.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcclientsdk.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcconstants.h"
|
||||
$File "$SRCDIR\public\gcsdk\gclogger.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcclientjob.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcmsg.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcsdk.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcschema.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcsession.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcsystemmsgs.h"
|
||||
$File "$SRCDIR\public\gcsdk\job.h"
|
||||
$File "$SRCDIR\public\gcsdk\jobmgr.h"
|
||||
$File "$SRCDIR\public\gcsdk\jobtime.h"
|
||||
$File "$SRCDIR\public\gcsdk\messagelist.h"
|
||||
$File "$SRCDIR\public\gcsdk\msgbase.h"
|
||||
$File "$SRCDIR\public\gcsdk\netpacket.h"
|
||||
$File "$SRCDIR\public\gcsdk\msgprotobuf.h"
|
||||
$File "$SRCDIR\public\gcsdk\netpacketpool.h"
|
||||
$File "$SRCDIR\public\gcsdk\protobufsharedobject.h"
|
||||
$File "$SRCDIR\public\gcsdk\refcount.h"
|
||||
$File "$SRCDIR\public\gcsdk\sharedobject.h"
|
||||
$File "$SRCDIR\public\gcsdk\sharedobjectcache.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcclient_sharedobjectcache.h"
|
||||
$File "$SRCDIR\public\gcsdk\workthreadpool.h"
|
||||
|
||||
$File "steamextra\clientenums.h"
|
||||
$File "steamextra\misc.h"
|
||||
$File "steamextra\rtime.h"
|
||||
$File "steamextra\gamecoordinator\igamecoordinator.h"
|
||||
$File "steamextra\gamecoordinator\igamecoordinatorhost.h"
|
||||
$File "steamextra\gamecoordinator\igcsqlquery.h"
|
||||
$File "steamextra\gamecoordinator\igcsqlresultsetlist.h"
|
||||
$File "steamextra\steam\isteamgamecoordinator.h"
|
||||
$File "steamextra\tier0\t0constants.h"
|
||||
$File "steamextra\tier1\pearsonshash.h"
|
||||
$File "steamextra\tier1\tsmempool.h"
|
||||
$File "steamextra\tier1\tsmultimempool.h"
|
||||
}
|
||||
|
||||
$Folder "Protobuf Files"
|
||||
{
|
||||
$File "gcsystemmsgs.proto"
|
||||
$Folder "Generated Files"
|
||||
{
|
||||
$DynamicFile "$GENERATED_PROTO_DIR/gcsystemmsgs.pb.h"
|
||||
$DynamicFile "$GENERATED_PROTO_DIR/gcsystemmsgs.pb.cc"
|
||||
{
|
||||
$Configuration
|
||||
{
|
||||
$Compiler
|
||||
{
|
||||
$Create/UsePrecompiledHeader "Not Using Precompiled Headers"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// gcsdk_include.vpc
|
||||
//
|
||||
// Project Script
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
$MacroRequired "PLATFORM"
|
||||
$MacroRequired GENERATED_PROTO_DIR
|
||||
|
||||
$include "$SRCDIR\vpc_scripts\protobuf_builder.vpc"
|
||||
$include "$SRCDIR\gcsdk\steammessages_include.vpc"
|
||||
|
||||
$Configuration
|
||||
{
|
||||
$General
|
||||
{
|
||||
$AdditionalProjectDependencies "gcsdk"
|
||||
}
|
||||
}
|
||||
|
||||
$Project
|
||||
{
|
||||
$File "$SRCDIR/gcsdk/gcsystemmsgs.proto"
|
||||
$Folder "Generated Files" [!$OSXALL]
|
||||
{
|
||||
$DynamicFile "$GENERATED_PROTO_DIR/gcsystemmsgs.pb.h"
|
||||
$DynamicFile "$GENERATED_PROTO_DIR/gcsystemmsgs.pb.cc"
|
||||
{
|
||||
$Configuration
|
||||
{
|
||||
$Compiler
|
||||
{
|
||||
$Create/UsePrecompiledHeader "Not Using Precompiled Headers"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$Folder "Link Libraries"
|
||||
{
|
||||
$Lib gcsdk
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// GCSDK_gc.VPC
|
||||
//
|
||||
// Project Script for the Game Coordinator SDK (GC version)
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
$MacroRequired "PLATSUBDIR"
|
||||
|
||||
// The OUTLIBDIR for this library has long used $PLATFORM in the path so
|
||||
// appending $PLATSUBDIR is redundant.
|
||||
$macro SRCDIR ".."
|
||||
$Macro OUTLIBDIR "$LIBPUBLIC"
|
||||
$Macro GENERATED_PROTO_DIR "$SRCDIR\gcsdk\generated_proto_gc"
|
||||
|
||||
$Linux
|
||||
{
|
||||
$BuildForLinux 1
|
||||
}
|
||||
|
||||
$include "$SRCDIR\vpc_scripts\source_lib_base.vpc"
|
||||
$include "$SRCDIR\vpc_scripts\protobuf_builder.vpc"
|
||||
$include "$SRCDIR\gcsdk\steammessages_include.vpc"
|
||||
$include "$SRCDIR\gcsdk\gcsdk_gcmessages_include.vpc"
|
||||
|
||||
$Configuration "Release"
|
||||
{
|
||||
$General
|
||||
{
|
||||
$IntermediateDirectory ".\Release_gc$PLATSUBDIR"
|
||||
}
|
||||
|
||||
$Compiler
|
||||
{
|
||||
$PreprocessorDefinitions "$BASE;RELEASEASSERTS"
|
||||
}
|
||||
}
|
||||
|
||||
$Configuration "Debug"
|
||||
{
|
||||
$General
|
||||
{
|
||||
$IntermediateDirectory ".\Debug_gc$PLATSUBDIR"
|
||||
}
|
||||
}
|
||||
|
||||
$Configuration
|
||||
{
|
||||
$Compiler
|
||||
{
|
||||
$PreprocessorDefinitions "$BASE;GC"
|
||||
$AdditionalIncludeDirectories "$BASE;$SRCDIR\gcsdk\steamextra;..;..\public;..\public\gcsdk;.;$SRCDIR\thirdparty\JSON_parser"
|
||||
$Create/UsePrecompiledHeader "Use Precompiled Header (/Yu)"
|
||||
$AdditionalOptions "$BASE /YlGCSDK_PCH_Symbol"
|
||||
}
|
||||
}
|
||||
|
||||
$Project "gcsdk_gc"
|
||||
{
|
||||
$Folder "Source Files"
|
||||
{
|
||||
// turn off the debug check for the debug build
|
||||
$File "$SRCDIR\common\debug_lib_check.cpp"
|
||||
{
|
||||
$Configuration "Debug"
|
||||
{
|
||||
$ExcludedFromBuild "Yes"
|
||||
}
|
||||
}
|
||||
|
||||
$File "stdafx.cpp"
|
||||
{
|
||||
$Configuration
|
||||
{
|
||||
$Compiler
|
||||
{
|
||||
$Create/UsePrecompiledHeader "Create Precompiled Header (/Yc)"
|
||||
|
||||
// Generate a bogus symbol to fix the PCH "no debug symbols" warning
|
||||
//$AdditionalOptions "$BASE /YlBogusGCSDKSymbol"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$File "accountdetails.cpp"
|
||||
$File "bufferpool.cpp"
|
||||
$File "directory.cpp"
|
||||
$File "framefunction.cpp"
|
||||
$File "gcbase.cpp"
|
||||
$File "gcconstants.cpp"
|
||||
$File "gc_convar.cpp"
|
||||
$File "gchost.cpp"
|
||||
$File "gcjob.cpp"
|
||||
$File "gcleaderboardapi.cpp"
|
||||
$File "gclogger.cpp"
|
||||
$File "gcmsg.cpp"
|
||||
$File "gcdirtyfield.cpp"
|
||||
$file "gcreportprinter.cpp"
|
||||
$File "gcsession.cpp"
|
||||
$File "gcsqlquery.cpp"
|
||||
$File "gcparalleljobfarm.cpp"
|
||||
$File "gcwebapi.cpp"
|
||||
$File "gcwebapikey.cpp"
|
||||
$File "gcwgjobmgr.cpp"
|
||||
$File "gc_sharedobjectcache.cpp"
|
||||
$File "http.cpp"
|
||||
$File "job.cpp"
|
||||
$File "jobmgr.cpp"
|
||||
$File "jobtime.cpp"
|
||||
$File "messagelist.cpp"
|
||||
$File "msgprotobuf.cpp"
|
||||
$File "netpacket.cpp"
|
||||
$File "netpacketpool.cpp"
|
||||
$File "protobufsharedobject.cpp"
|
||||
$File "scheduledfunction.cpp"
|
||||
$File "schemasharedobject.cpp"
|
||||
$File "sdocache.cpp"
|
||||
$File "sharedobject.cpp"
|
||||
$File "sharedobjectcache.cpp"
|
||||
$File "sharedobjecttransaction.cpp"
|
||||
$File "webapi_response.cpp"
|
||||
$File "workthreadpool.cpp"
|
||||
$File "$SRCDIR\thirdparty\JSON_parser\JSON_parser.c"
|
||||
{
|
||||
$Configuration
|
||||
{
|
||||
$Compiler
|
||||
{
|
||||
$Create/UsePrecompiledHeader "Not Using Precompiled Headers"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$File "steamextra\misc.cpp"
|
||||
$File "steamextra\rtime.cpp"
|
||||
$File "steamextra\steamid.cpp"
|
||||
$File "steamextra\tier1\hashglobals.cpp"
|
||||
$File "steamextra/tier1/murmurhash3.cpp"
|
||||
$File "steamextra\tier1\tsmempool.cpp"
|
||||
$File "steamextra\tier1\tsmultimempool.cpp"
|
||||
|
||||
$Folder "SQLAccess"
|
||||
{
|
||||
$File "sqlaccess\columnset.cpp"
|
||||
$File "sqlaccess\record.cpp"
|
||||
$File "sqlaccess\recordinfo.cpp"
|
||||
$File "sqlaccess\schema.cpp"
|
||||
$File "sqlaccess\schemafull.cpp"
|
||||
$File "sqlaccess\schemaupdate.cpp"
|
||||
$File "sqlaccess\sqlaccess.cpp"
|
||||
$File "sqlaccess\sqlrecord.cpp"
|
||||
$File "sqlaccess\sqlutil.cpp"
|
||||
}
|
||||
}
|
||||
|
||||
$Folder "Header Files"
|
||||
{
|
||||
$File "stdafx.h"
|
||||
|
||||
$File "$SRCDIR\public\gcsdk\accountdetails.h"
|
||||
$File "$SRCDIR\public\gcsdk\bufferpool.h"
|
||||
$File "$SRCDIR/public/gcsdk/directory.h"
|
||||
$File "$SRCDIR\public\gcsdk\enumutils.h"
|
||||
$File "$SRCDIR\public\gcsdk\framefunction.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcbase.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcclient.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcclientsdk.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcconstants.h"
|
||||
$File "$SRCDIR\public\gcsdk\gc_convar.h"
|
||||
$File "$SRCDIR\public\gcsdk\gclogger.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcleaderboardapi.h"
|
||||
$File "$SRCDIR\public\gcsdk\gchost.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcjob.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcclientjob.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcmsg.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcdirtyfield.h"
|
||||
$File "$SRCDIR/public/gcsdk/gcreportprinter.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcsdk.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcschema.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcsession.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcsqlquery.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcsqlwritequeue.h"
|
||||
$File "$SRCDIR/public/gcsdk/gcsystemaccess.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcsystemmsgs.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcparalleljobfarm.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcwebapi.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcwebapikey.h"
|
||||
$File "$SRCDIR\public\gcsdk\gcwgjobmgr.h"
|
||||
$File "$SRCDIR\public\gcsdk\gc_sharedobjectcache.h"
|
||||
$File "$SRCDIR\public\gcsdk\http.h"
|
||||
$File "$SRCDIR\public\gcsdk\job.h"
|
||||
$File "$SRCDIR\public\gcsdk\jobmgr.h"
|
||||
$File "$SRCDIR\public\gcsdk\jobtime.h"
|
||||
$File "$SRCDIR\public\gcsdk\messagelist.h"
|
||||
$File "$SRCDIR\public\gcsdk\msgbase.h"
|
||||
$File "$SRCDIR\public\gcsdk\netpacket.h"
|
||||
$File "$SRCDIR\public\gcsdk\msgprotobuf.h"
|
||||
$File "$SRCDIR\public\gcsdk\netpacketpool.h"
|
||||
$File "$SRCDIR\public\gcsdk\protobufsharedobject.h"
|
||||
$File "$SRCDIR\public\gcsdk\refcount.h"
|
||||
$File "$SRCDIR\public\gcsdk\scheduledfunction.h"
|
||||
$File "$SRCDIR\public\gcsdk\schemasharedobject.h"
|
||||
$File "$SRCDIR\public\gcsdk\sdocache.h"
|
||||
$File "$SRCDIR\public\gcsdk\sharedobject.h"
|
||||
$File "$SRCDIR\public\gcsdk\sharedobjectcache.h"
|
||||
$File "$SRCDIR\public\gcsdk\sharedobjecttransaction.h"
|
||||
$File "$SRCDIR\public\gcsdk\webapi_response.h"
|
||||
$File "$SRCDIR\public\gcsdk\workthreadpool.h"
|
||||
|
||||
$File "steamextra\clientenums.h"
|
||||
$File "steamextra\misc.h"
|
||||
$File "steamextra\rtime.h"
|
||||
$File "steamextra\gamecoordinator\igamecoordinator.h"
|
||||
$File "steamextra\gamecoordinator\igamecoordinatorhost.h"
|
||||
$File "steamextra\gamecoordinator\igcsqlquery.h"
|
||||
$File "steamextra\gamecoordinator\igcsqlresultsetlist.h"
|
||||
$File "steamextra\steam\isteamgamecoordinator.h"
|
||||
$File "steamextra\tier0\t0constants.h"
|
||||
$File "steamextra/tier1/murmurhash3.h"
|
||||
$File "steamextra\tier1\pearsonshash.h"
|
||||
$File "steamextra\tier1\tsmempool.h"
|
||||
$File "steamextra\tier1\tsmultimempool.h"
|
||||
$File "steamextra\tier1\utlhashmaplarge.h"
|
||||
$File "$SRCDIR\thirdparty\JSON_parser\JSON_parser.h"
|
||||
|
||||
$Folder "SQLAccess"
|
||||
{
|
||||
$File "$SRCDIR\public\gcsdk\sqlaccess\columnset.h"
|
||||
$File "$SRCDIR\public\gcsdk\sqlaccess\record.h"
|
||||
$File "$SRCDIR\public\gcsdk\sqlaccess\recordinfo.h"
|
||||
$File "$SRCDIR\public\gcsdk\sqlaccess\schema.h"
|
||||
$File "$SRCDIR\public\gcsdk\sqlaccess\schemafull.h"
|
||||
$File "$SRCDIR\public\gcsdk\sqlaccess\schemaupdate.h"
|
||||
$File "$SRCDIR\public\gcsdk\sqlaccess\sqlaccess.h"
|
||||
$File "$SRCDIR\public\gcsdk\sqlaccess\sqlrecord.h"
|
||||
$File "$SRCDIR\public\gcsdk\sqlaccess\sqlutil.h"
|
||||
}
|
||||
}
|
||||
|
||||
$Folder "Protobuf Files"
|
||||
{
|
||||
$File "gcsystemmsgs.proto"
|
||||
$Folder "Generated Files"
|
||||
{
|
||||
$DynamicFile "$GENERATED_PROTO_DIR/gcsystemmsgs.pb.h"
|
||||
$DynamicFile "$GENERATED_PROTO_DIR/gcsystemmsgs.pb.cc"
|
||||
{
|
||||
$Configuration
|
||||
{
|
||||
$Compiler
|
||||
{
|
||||
$Create/UsePrecompiledHeader "Not Using Precompiled Headers"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// gcsdk_include.vpc
|
||||
//
|
||||
// Project Script
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
$MacroRequired "PLATFORM"
|
||||
$MacroRequired GENERATED_PROTO_DIR
|
||||
|
||||
$include "$SRCDIR\vpc_scripts\protobuf_builder.vpc"
|
||||
$include "$SRCDIR\gcsdk\steammessages_include.vpc"
|
||||
$include "$SRCDIR\game\protobuf_include.vpc"
|
||||
|
||||
|
||||
$Configuration
|
||||
{
|
||||
$General
|
||||
{
|
||||
$AdditionalProjectDependencies "gcsdk_gc"
|
||||
}
|
||||
}
|
||||
|
||||
$Project
|
||||
{
|
||||
$File "$SRCDIR/gcsdk/gcsystemmsgs.proto"
|
||||
$Folder "Generated Files"
|
||||
{
|
||||
$DynamicFile "$GENERATED_PROTO_DIR/gcsystemmsgs.pb.h"
|
||||
$DynamicFile "$GENERATED_PROTO_DIR/gcsystemmsgs.pb.cc"
|
||||
{
|
||||
$Configuration
|
||||
{
|
||||
$Compiler
|
||||
{
|
||||
$Create/UsePrecompiledHeader "Not Using Precompiled Headers"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$Folder "Link Libraries"
|
||||
{
|
||||
$Lib gcsdk_gc
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
//====== Copyright 1996-2010, Valve Corporation, All rights reserved. =======
|
||||
//
|
||||
// Purpose: The file defines our Google Protocol Buffers which are used in over
|
||||
// the wire messages between servers as well as between the TF GC and TF gameservers
|
||||
// and clients.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
// We care more about speed than code size
|
||||
option optimize_for = SPEED;
|
||||
|
||||
// We don't use the service generation functionality
|
||||
option cc_generic_services = false;
|
||||
|
||||
|
||||
//
|
||||
// STYLE NOTES:
|
||||
//
|
||||
// Use CamelCase CMsgMyMessageName style names for messages.
|
||||
//
|
||||
// Use lowercase _ delimited names like my_steam_id for field names, this is non-standard for Steam,
|
||||
// but plays nice with the Google formatted code generation.
|
||||
//
|
||||
// Try not to use required fields ever. Only do so if you are really really sure you'll never want them removed.
|
||||
// Optional should be preffered as it will make versioning easier and cleaner in the future if someone refactors
|
||||
// your message and wants to remove or rename fields.
|
||||
//
|
||||
// Use fixed64 for JobId_t, GID_t, or SteamID. This is appropriate for any field that is normally
|
||||
// going to be larger than 2^56. Otherwise use int64 for 64 bit values that are frequently smaller
|
||||
// than 2^56 as it will safe space on the wire in those cases.
|
||||
//
|
||||
// Similar to fixed64, use fixed32 for RTime32 or other 32 bit values that are frequently larger than
|
||||
// 2^28. It will safe space in those cases, otherwise use int32 which will safe space for smaller values.
|
||||
// An exception to this rule for RTime32 is if the value will frequently be zero rather than set to an actual
|
||||
// time.
|
||||
//
|
||||
|
||||
import "steammessages.proto";
|
||||
|
||||
message CMsgSOIDOwner
|
||||
{
|
||||
optional uint32 type = 1;
|
||||
optional uint64 id = 2;
|
||||
}
|
||||
|
||||
//
|
||||
// k_ESOMsg_Create
|
||||
// k_ESOMsg_Update
|
||||
// k_ESOMsg_Destroy
|
||||
//
|
||||
message CMsgSOSingleObject
|
||||
{
|
||||
optional fixed64 owner = 1; // the steam ID of the owner of this object
|
||||
optional int32 type_id = 2; // the shared object type ID of this object
|
||||
optional bytes object_data = 3; // the actual data for the object
|
||||
optional fixed64 version = 4; // version of the cache
|
||||
|
||||
optional CMsgSOIDOwner owner_soid = 5; // The SOID that owns this object
|
||||
optional uint32 service_id = 6; // The service that sent this update
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// k_ESOMsg_UpdateMultiple
|
||||
//
|
||||
message CMsgSOMultipleObjects
|
||||
{
|
||||
message SingleObject
|
||||
{
|
||||
optional int32 type_id = 1; // the shared object type ID of this object
|
||||
optional bytes object_data = 2; // the actual data for the object
|
||||
};
|
||||
optional fixed64 owner = 1; // the steam ID of the owner of the objects
|
||||
repeated SingleObject objects = 2; // a list of types
|
||||
optional fixed64 version = 3; // version of the cache
|
||||
optional CMsgSOIDOwner owner_soid = 6; // The SOID that owns this object
|
||||
optional uint32 service_id = 7; // The service that sent this update
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// k_ESOMsg_CacheSubscribed
|
||||
//
|
||||
message CMsgSOCacheSubscribed
|
||||
{
|
||||
message SubscribedType
|
||||
{
|
||||
optional int32 type_id = 1; // ID of the type for these objects
|
||||
repeated bytes object_data = 2; // the data for all the objects of this type
|
||||
};
|
||||
|
||||
optional fixed64 owner = 1; // the owner of this cache
|
||||
repeated SubscribedType objects = 2; // a list of types
|
||||
optional fixed64 version = 3; // version of the cache
|
||||
optional CMsgSOIDOwner owner_soid = 4; // The SOID that owns this object
|
||||
optional uint32 service_id = 5; // The service that is providing this SO cache information
|
||||
repeated uint32 service_list = 6; // Other services that are providing parts of this cache and need to to be received for it to be complete
|
||||
optional fixed64 sync_version = 7; // The unique ID of the sync to ensure all subscribes across services match. Ignored/not provided if no other GCs are involved
|
||||
};
|
||||
|
||||
//
|
||||
// k_ESOMsg_CacheSubscribedUpToDate
|
||||
//
|
||||
message CMsgSOCacheSubscribedUpToDate
|
||||
{
|
||||
optional fixed64 version = 1; // version of the cache for this sub GC
|
||||
optional CMsgSOIDOwner owner_soid = 2; // The SOID that owns this object
|
||||
optional uint32 service_id = 3; // The service that is providing this SO cache information
|
||||
repeated uint32 service_list = 4; // Other services that are providing parts of this cache and need to to be received for it to be complete
|
||||
optional fixed64 sync_version = 5; // The unique ID of the sync to ensure all subscribes across services match. Ignored/not provided if no other GCs are involved
|
||||
};
|
||||
|
||||
//
|
||||
// k_ESOMsg_CacheUnsubscribed
|
||||
//
|
||||
message CMsgSOCacheUnsubscribed
|
||||
{
|
||||
optional fixed64 owner = 1; // the owner of this cache
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// k_ESOMsg_CacheSubscriptionCheck
|
||||
//
|
||||
message CMsgSOCacheSubscriptionCheck
|
||||
{
|
||||
optional fixed64 owner = 1; // the owner of the cache
|
||||
optional fixed64 version = 2; // version of the cache
|
||||
optional CMsgSOIDOwner owner_soid = 3; // The SOID that owns this object
|
||||
optional uint32 service_id = 4; // The service associated with this version
|
||||
repeated uint32 service_list = 5; // The other services that need to provide information on this cache
|
||||
optional fixed64 sync_version = 6; // The unique ID of the sync to ensure all subscribes across services match. Ignored/not provided if no other GCs are involved
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// k_ESOMsg_CacheSubscriptionRefresh
|
||||
//
|
||||
message CMsgSOCacheSubscriptionRefresh
|
||||
{
|
||||
optional fixed64 owner = 1; // the owner of the cache
|
||||
optional CMsgSOIDOwner owner_soid = 2; // The SOID that owns this object
|
||||
};
|
||||
|
||||
//
|
||||
// Stored in memcached for each SO Cache
|
||||
//
|
||||
message CMsgSOCacheVersion
|
||||
{
|
||||
optional fixed64 version = 1; // version of the cache
|
||||
};
|
||||
|
||||
enum PartnerAccountType
|
||||
{
|
||||
//the default for users, which is an account not linked to a provider
|
||||
PARTNER_NONE = 0;
|
||||
//linked to a Perfect World account
|
||||
PARTNER_PERFECT_WORLD = 1;
|
||||
//linked to a Nexon account
|
||||
PARTNER_NEXON = 2;
|
||||
};
|
||||
|
||||
message CMsgGCMultiplexMessage
|
||||
{
|
||||
optional uint32 msgtype = 1; // ID of the message being sent
|
||||
optional bytes payload = 2; // Serialized message to send
|
||||
repeated fixed64 steamids = 3; // Clients to send the message to
|
||||
};
|
||||
|
||||
|
||||
// k_EGCToGCMsgMasterAck
|
||||
message CGCToGCMsgMasterAck
|
||||
{
|
||||
optional uint32 dir_index = 1; // the index from the directory that this GC is responsible for
|
||||
optional string machine_name = 3; // the machine name that this GC is running on so that we can quickly see the universe configuration
|
||||
optional string process_name = 4; // the name of the process that this is running to ensure that it matches
|
||||
repeated uint32 type_instances = 5; // the list of type instances contained within this process, must match the master directory
|
||||
};
|
||||
|
||||
// k_EGCToGCMsgMasterAck_Response
|
||||
message CGCToGCMsgMasterAck_Response
|
||||
{
|
||||
optional int32 eresult = 1 [default = 2]; // Is this GC what the master thinks it should be?
|
||||
};
|
||||
|
||||
// k_EGCToGCMsgMasterStartupComplete
|
||||
message CGCToGCMsgMasterStartupComplete
|
||||
{
|
||||
message GCInfo
|
||||
{
|
||||
optional uint32 dir_index = 1;
|
||||
optional string machine_name = 2;
|
||||
};
|
||||
|
||||
repeated GCInfo gc_info = 1;
|
||||
};
|
||||
|
||||
|
||||
// k_EGCToGCMsgRouted
|
||||
message CGCToGCMsgRouted
|
||||
{
|
||||
optional uint32 msg_type = 1; // the type of the contained message
|
||||
optional fixed64 sender_id = 2; // steam ID of the client that sent this. If nil, it came from the system instead.
|
||||
optional bytes net_message = 3; // the binary blob contents of the contained message
|
||||
};
|
||||
|
||||
// k_EGCToGCMsgRoutedReply
|
||||
message CGCToGCMsgRoutedReply
|
||||
{
|
||||
optional uint32 msg_type = 1; // the type of the contained message
|
||||
optional bytes net_message = 2; // the binary blob contents of the contained message
|
||||
};
|
||||
|
||||
// k_EMsgGCUpdateSubGCSessionInfo
|
||||
message CMsgGCUpdateSubGCSessionInfo
|
||||
{
|
||||
message CMsgUpdate
|
||||
{
|
||||
optional fixed64 steamid = 1;
|
||||
optional fixed32 ip = 2;
|
||||
optional bool trusted = 3;
|
||||
};
|
||||
|
||||
repeated CMsgUpdate updates = 1; // the list of updates we should process (in order)
|
||||
}
|
||||
|
||||
// k_EMsgGCRequestSubGCSessionInfo
|
||||
message CMsgGCRequestSubGCSessionInfo
|
||||
{
|
||||
optional fixed64 steamid = 1;
|
||||
}
|
||||
|
||||
// k_EMsgGCRequestSubGCSessionInfoRespone
|
||||
message CMsgGCRequestSubGCSessionInfoResponse
|
||||
{
|
||||
optional fixed32 ip = 1;
|
||||
optional bool trusted = 2;
|
||||
}
|
||||
|
||||
// k_EMsgGCToGCIncrementRecruitmentLevel
|
||||
message CMsgGCToGCIncrementRecruitmentLevel
|
||||
{
|
||||
optional fixed64 steamid = 1;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// CMsgSOCacheHaveVersion
|
||||
//
|
||||
// An array of these is sent to indicate what SO cache versions we currently have
|
||||
//
|
||||
message CMsgSOCacheHaveVersion
|
||||
{
|
||||
optional CMsgSOIDOwner soid = 1; // ID of the cache we have
|
||||
optional fixed64 version = 2; // version stamp we have of this cache
|
||||
optional uint32 service_id = 3; // For partial caches, what service this version is associated with
|
||||
};
|
||||
|
||||
// !FIXME! DOTAMERGE
|
||||
////
|
||||
//// CMsgClientHello
|
||||
////
|
||||
//message CMsgClientHello
|
||||
//{
|
||||
// optional uint32 version = 1;
|
||||
// repeated CMsgSOCacheHaveVersion socache_have_versions = 2;
|
||||
//
|
||||
// /// Game-specific value indicating what state the client is in,
|
||||
// /// and what sort of session is wants. This is used to decide
|
||||
// /// whether the client even needs a session, and if so, what
|
||||
// /// priority we should assign
|
||||
// optional uint32 client_session_need = 3;
|
||||
//
|
||||
// /// What special partner-specific launcher was used, if any?
|
||||
// optional PartnerAccountType client_launcher = 4;
|
||||
//
|
||||
// /// Secret Key used to gain elevated status with the GC
|
||||
// optional string secret_key = 5;
|
||||
//};
|
||||
//
|
||||
////
|
||||
//// CMsgClientWelcome
|
||||
////
|
||||
//message CMsgClientWelcome
|
||||
//{
|
||||
// optional uint32 version = 1;
|
||||
// optional bytes game_data = 2;
|
||||
//
|
||||
// // List of caches to which the client is now subscribed, but
|
||||
// // the GC thinks we don't have the latest data so it is included it here.
|
||||
// repeated CMsgSOCacheSubscribed outofdate_subscribed_caches = 3;
|
||||
//
|
||||
// // List of caches to which the client is now subscribed, and the
|
||||
// // GC thinks that we already have the latest version. (It is sending
|
||||
// // the version number just to make sure.)
|
||||
// repeated CMsgSOCacheSubscriptionCheck uptodate_subscribed_caches = 4;
|
||||
//
|
||||
// // location information
|
||||
// message Location
|
||||
// {
|
||||
// optional float latitude = 1;
|
||||
// optional float longitude = 2;
|
||||
// optional string country = 3;
|
||||
// }
|
||||
//
|
||||
// optional Location location = 5;
|
||||
//
|
||||
// optional bytes save_game_key = 6;
|
||||
//};
|
||||
|
||||
/// Status of connection to GC.
|
||||
//
|
||||
// Don't change these values, as they need to be consistent across props, since they
|
||||
// are central to handshaking with clients old and new!
|
||||
enum GCConnectionStatus
|
||||
{
|
||||
GCConnectionStatus_HAVE_SESSION = 0; // You have a session. (The status of one or more services may have changed.)
|
||||
GCConnectionStatus_GC_GOING_DOWN = 1; // Notification that the entire GC system is going down.
|
||||
GCConnectionStatus_NO_SESSION = 2; // You don't have a session. Please send a hello message to initiate the session creation process.
|
||||
GCConnectionStatus_NO_SESSION_IN_LOGON_QUEUE = 3; // You don't have a session, but we have your info and will sign you on ASAP. No need to send it again.
|
||||
GCConnectionStatus_NO_STEAM = 4; // Client-side only value. We aren't connected to Steam.
|
||||
GCConnectionStatus_SUSPENDED = 5; // Client-side only value. We were told that we've been suspended from connecting to this GC. No need to try again later.
|
||||
};
|
||||
|
||||
//
|
||||
// CMsgConnectionStatus
|
||||
//
|
||||
message CMsgConnectionStatus
|
||||
{
|
||||
optional GCConnectionStatus status = 1;
|
||||
optional uint32 client_session_need = 2; // The last client session need state we got from you.
|
||||
|
||||
//
|
||||
// If they are in the queue, this tells them where they are.
|
||||
//
|
||||
optional int32 queue_position = 3; // your approximate position in the queue
|
||||
optional int32 queue_size = 4; // how many people are in the queue
|
||||
optional int32 wait_seconds = 5; // how long you've been waiting
|
||||
optional int32 estimated_wait_seconds_remaining = 6; // estimated time until you are logged on.
|
||||
|
||||
// TODO: Here we could include a list of services
|
||||
// (perhaps just SO cache type ID's?) that
|
||||
// are currently known to be offline.
|
||||
};
|
||||
|
||||
//k_EMsgGCToGCSOCacheSubscribe
|
||||
message CMsgGCToGCSOCacheSubscribe
|
||||
{
|
||||
message CMsgHaveVersions
|
||||
{
|
||||
optional uint32 service_id = 1;
|
||||
optional uint64 version = 2;
|
||||
};
|
||||
|
||||
optional fixed64 subscriber = 1; // the ID of the user that is subscribing to a cache
|
||||
optional fixed64 subscribe_to = 2; // the ID of the cache that is being subscribed to
|
||||
optional fixed64 sync_version = 3; // the unique ID that is used to distinguish this synch across the various parts
|
||||
repeated CMsgHaveVersions have_versions = 4; // the version that the client has already (used to skip unnecessary sends)
|
||||
};
|
||||
|
||||
//k_EMsgGCToGCSOCacheUnsubscribe
|
||||
message CMsgGCToGCSOCacheUnsubscribe
|
||||
{
|
||||
optional fixed64 subscriber = 1; // if 0, then this means unsubscribe everyone from the cache
|
||||
optional fixed64 unsubscribe_from = 2; // the cache we are unsubscribing from
|
||||
};
|
||||
|
||||
// k_EMsgGCPingRequest
|
||||
// k_EMsgGCPingReply
|
||||
message CMsgGCClientPing
|
||||
{
|
||||
// Remember, if you need to associate ping replies with requests, consider using the JobID field
|
||||
};
|
||||
|
||||
// Do not remove this comment due to a bug on the Mac OS X protobuf compiler
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
$Project
|
||||
{
|
||||
$Folder "Protobuf Files"
|
||||
{
|
||||
$File "$SRCDIR\gcsdk\gcsdk_gcmessages.proto"
|
||||
$Folder "Generated Files"
|
||||
{
|
||||
$DynamicFile "$GENERATED_PROTO_DIR\gcsdk_gcmessages.pb.h"
|
||||
$DynamicFile "$GENERATED_PROTO_DIR\gcsdk_gcmessages.pb.cc"
|
||||
{
|
||||
$Configuration
|
||||
{
|
||||
$Compiler
|
||||
{
|
||||
$Create/UsePrecompiledHeader "Not Using Precompiled Headers"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
// Include the gcsdk static lib, excluded from builds as appropriate.
|
||||
$Folder "Link Libraries"
|
||||
{
|
||||
$File "$SRCDIR\lib\$PLATFORM\release\gcsdk$_STATICLIB_EXT"
|
||||
{
|
||||
$Configuration "Debug" { $ExcludedFromBuild "Yes" }
|
||||
}
|
||||
$File "$SRCDIR\lib\$PLATFORM\debug\gcsdk$_STATICLIB_EXT"
|
||||
{
|
||||
$Configuration "Release" { $ExcludedFromBuild "Yes" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,607 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Holds the CGCSession class
|
||||
//
|
||||
//=============================================================================
|
||||
#include "stdafx.h"
|
||||
#include "gcsession.h"
|
||||
#include "steamextra/rtime.h"
|
||||
#include "gcsdk_gcmessages.pb.h"
|
||||
#include "gcsdk/gcreportprinter.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
// Probably this makes more sense true by default, but we're spewing a ton and Fletcher says it
|
||||
// isn't a big deal for TF so here we go.
|
||||
GCConVar gs_session_assert_valid_addr_and_port( "gs_session_assert_valid_addr_and_port", "0" );
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
DECLARE_GC_EMIT_GROUP( g_EGSessions, sessions );
|
||||
DECLARE_GC_EMIT_GROUP_DEFAULTS( g_EGRateLimit, ratelimit, 2, 3 );
|
||||
|
||||
GCConVar max_user_messages_per_second( "max_user_messages_per_second", "20", 0, "Maximum number of messages a user can send per second. 0 disables the rate limiting" );
|
||||
static GCConVar user_message_rate_limit_warning_period( "user_message_rate_limit_warning_period", "30", 0, "Number of seconds between warning about rate limiting for users" );
|
||||
|
||||
static GCConVar msg_rate_limit_report_user_bucket_1( "msg_rate_limit_report_user_bucket_1", "10", 0, "These values control where various users are bucketed in rate limiting reports to help identify how frequently users are running into rate limiting" );
|
||||
static GCConVar msg_rate_limit_report_user_bucket_2( "msg_rate_limit_report_user_bucket_2", "100", 0, "These values control where various users are bucketed in rate limiting reports to help identify how frequently users are running into rate limiting" );
|
||||
|
||||
static GCConVar msg_rate_limit_list_user( "msg_rate_limit_list_user", "0", 0, "When set to a user account ID, this will report all the messages that are rate limited for that user to the console" );
|
||||
|
||||
CMsgRateLimitTracker g_RateLimitTracker;
|
||||
|
||||
|
||||
CMsgRateLimitTracker::CMsgRateLimitTracker() :
|
||||
m_StartTime( CRTime::RTime32TimeCur() )
|
||||
{
|
||||
}
|
||||
|
||||
void CMsgRateLimitTracker::TrackRateLimitedMsg( const CSteamID steamID, MsgType_t eMsgType )
|
||||
{
|
||||
//update message stat
|
||||
{
|
||||
uint32 nMsgIndex = m_MsgStats.Find( eMsgType );
|
||||
if( !m_MsgStats.IsValidIndex( nMsgIndex ) )
|
||||
{
|
||||
nMsgIndex = m_MsgStats.Insert( eMsgType, 0 );
|
||||
}
|
||||
m_MsgStats[ nMsgIndex ]++;
|
||||
}
|
||||
|
||||
//update user stats
|
||||
{
|
||||
uint32 nUserIndex = m_UserStats.Find( steamID );
|
||||
if( !m_UserStats.IsValidIndex( nUserIndex ) )
|
||||
{
|
||||
nUserIndex = m_UserStats.Insert( steamID, 0 );
|
||||
}
|
||||
m_UserStats[ nUserIndex ]++;
|
||||
}
|
||||
|
||||
//determine the severity to output the warning at. Assume verbose unless we are tracking a specific account ID (note that no account has 0 so 0 still effectively turns it off)
|
||||
CGCEmitGroup::EMsgLevel eMsgLevel = CGCEmitGroup::kMsg_Verbose;
|
||||
if( ( uint32 )msg_rate_limit_list_user.GetInt() == steamID.GetAccountID() )
|
||||
{
|
||||
eMsgLevel = CGCEmitGroup::kMsg_Msg;
|
||||
}
|
||||
EG_EMIT( g_EGMessages, eMsgLevel, "Dropped message %s (%d) for user %s\n", PchMsgNameFromEMsg( eMsgType ), eMsgType, steamID.Render() );
|
||||
}
|
||||
|
||||
void CMsgRateLimitTracker::ReportMsgStats() const
|
||||
{
|
||||
CGCReportPrinter rp;
|
||||
rp.AddStringColumn( "Msg" );
|
||||
rp.AddIntColumn( "Count", CGCReportPrinter::eSummary_Total );
|
||||
|
||||
FOR_EACH_MAP_FAST( m_MsgStats, nCurrMsg )
|
||||
{
|
||||
rp.StrValue( PchMsgNameFromEMsg( m_MsgStats.Key( nCurrMsg ) ) );
|
||||
rp.IntValue( m_MsgStats[ nCurrMsg ] );
|
||||
rp.CommitRow();
|
||||
}
|
||||
|
||||
rp.SortReport( "Count" );
|
||||
rp.PrintReport( SPEW_CONSOLE );
|
||||
}
|
||||
|
||||
void CMsgRateLimitTracker::ReportTopUsers( uint32 nMinMsgs, uint32 nListTop ) const
|
||||
{
|
||||
//collect a list of all messages, and sort them into order of frequency
|
||||
CGCReportPrinter rp;
|
||||
rp.AddSteamIDColumn( "User" );
|
||||
rp.AddIntColumn( "Count", CGCReportPrinter::eSummary_Total );
|
||||
|
||||
FOR_EACH_MAP_FAST( m_UserStats, nCurrMsg )
|
||||
{
|
||||
rp.SteamIDValue( m_UserStats.Key( nCurrMsg ) );
|
||||
rp.IntValue( m_UserStats[ nCurrMsg ] );
|
||||
rp.CommitRow();
|
||||
}
|
||||
|
||||
rp.SortReport( "Count" );
|
||||
rp.PrintReport( SPEW_CONSOLE, nListTop );
|
||||
}
|
||||
|
||||
void CMsgRateLimitTracker::ReportUserStats() const
|
||||
{
|
||||
//run through the users and aggregate stats
|
||||
const uint32 nBucketLimit1 = ( uint32 )max( 0, min( msg_rate_limit_report_user_bucket_1.GetInt(), msg_rate_limit_report_user_bucket_2.GetInt() ) );
|
||||
const uint32 nBucketLimit2 = ( uint32 )max( 0, max( msg_rate_limit_report_user_bucket_1.GetInt(), msg_rate_limit_report_user_bucket_2.GetInt() ) );
|
||||
|
||||
uint32 nTotalMsg = 0;
|
||||
uint32 nMaxUser = 0;
|
||||
uint32 nBucketCount1 = 0;
|
||||
uint32 nBucketCount2 = 0;
|
||||
FOR_EACH_MAP_FAST( m_UserStats, nCurrMsg )
|
||||
{
|
||||
//add user counts to the buckets
|
||||
const uint32 nMsgs = m_UserStats[ nCurrMsg ];
|
||||
if( nMsgs <= nBucketLimit1 )
|
||||
nBucketCount1++;
|
||||
else if( nMsgs <= nBucketLimit2 )
|
||||
nBucketCount2++;
|
||||
|
||||
//add up our total number of offenses
|
||||
nTotalMsg += nMsgs;
|
||||
nMaxUser = max( nMaxUser, nMsgs );
|
||||
}
|
||||
|
||||
EG_MSG( SPEW_CONSOLE, "Capture Duration: %ds\n", CRTime::RTime32TimeCur() - m_StartTime );
|
||||
EG_MSG( SPEW_CONSOLE, "Total Dropped Messages: %d\n", nTotalMsg );
|
||||
EG_MSG( SPEW_CONSOLE, "Message IDs: %d\n", m_MsgStats.Count() );
|
||||
EG_MSG( SPEW_CONSOLE, "Users: %d (peak: %d)\n", m_UserStats.Count(), nMaxUser );
|
||||
EG_MSG( SPEW_CONSOLE, " Below %d msgs: %d\n", nBucketLimit1, nBucketCount1 );
|
||||
EG_MSG( SPEW_CONSOLE, " Below %d msgs: %d\n", nBucketLimit2, nBucketCount2 );
|
||||
}
|
||||
|
||||
void CMsgRateLimitTracker::ClearStats()
|
||||
{
|
||||
m_StartTime = CRTime::RTime32TimeCur();
|
||||
m_UserStats.RemoveAll();
|
||||
m_MsgStats.RemoveAll();
|
||||
}
|
||||
|
||||
//console command hooks
|
||||
GC_CON_COMMAND( msg_rate_limit_dump, "Dumps stats about rate limiting of messages" )
|
||||
{
|
||||
g_RateLimitTracker.ReportUserStats();
|
||||
g_RateLimitTracker.ReportMsgStats();
|
||||
g_RateLimitTracker.ReportTopUsers( 0, 20 );
|
||||
}
|
||||
|
||||
GC_CON_COMMAND( msg_rate_limit_dump_users, "Dumps a list of users that have been rate limited. Optional parameters can specify the number to dump or the minimum number of messages required." )
|
||||
{
|
||||
if( args.ArgC() < 3 )
|
||||
{
|
||||
EG_MSG( SPEW_CONSOLE, "Proper usage is: %s <min messages> <top users> - Specify 0 for one or both to have it be ignored\n", args[ 0 ] );
|
||||
return;
|
||||
}
|
||||
g_RateLimitTracker.ReportTopUsers( ( uint32 )max( 0, atoi( args[ 1 ] ) ), ( uint32 )max( 0, atoi( args[ 2 ] ) ) );
|
||||
}
|
||||
|
||||
GC_CON_COMMAND( msg_rate_limit_dump_msgs, "Dumps a list of messages that have been rate limited." )
|
||||
{
|
||||
g_RateLimitTracker.ReportMsgStats();
|
||||
}
|
||||
|
||||
GC_CON_COMMAND( msg_rate_limit_clear, "Clears all the accumulated msg rate limit stats" )
|
||||
{
|
||||
g_RateLimitTracker.ClearStats();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
// CSteamIDRateLimit
|
||||
//------------------------------------------------------------------------------------------
|
||||
|
||||
CSteamIDRateLimit::CSteamIDRateLimit( const GCConVar& cvNumPerPeriod, const GCConVar* pcvPeriodS ) :
|
||||
m_cvNumPerPeriod( cvNumPerPeriod ),
|
||||
m_pcvPeriodS( pcvPeriodS ),
|
||||
m_LastClear( CRTime::RTime32TimeCur() ),
|
||||
m_FrameFunction( "SteamIDRateLimit", CBaseFrameFunction::k_EFrameType_RunOnce )
|
||||
{
|
||||
m_FrameFunction.Register( this, &CSteamIDRateLimit::OnFrameFn );
|
||||
}
|
||||
|
||||
CSteamIDRateLimit::~CSteamIDRateLimit()
|
||||
{
|
||||
}
|
||||
|
||||
bool CSteamIDRateLimit::BIsRateLimited( CSteamID steamID, uint32 unMsgType )
|
||||
{
|
||||
int nIndex = m_Msgs.FindOrInsert( steamID, 0 );
|
||||
if( ++m_Msgs[ nIndex ] >= ( uint32 )m_cvNumPerPeriod.GetInt() )
|
||||
{
|
||||
g_RateLimitTracker.TrackRateLimitedMsg( steamID, unMsgType );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CSteamIDRateLimit::OnFrameFn( const CLimitTimer& timer )
|
||||
{
|
||||
//if no period is specified, assume one second
|
||||
int nIntervalS = ( m_pcvPeriodS ) ? MAX( 1, m_pcvPeriodS->GetInt() ) : 1;
|
||||
if( CRTime::RTime32TimeCur() >= m_LastClear + nIntervalS )
|
||||
{
|
||||
m_Msgs.RemoveAll();
|
||||
m_LastClear = CRTime::RTime32TimeCur();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CGCSession::CGCSession( const CSteamID & steamID, CGCSharedObjectCache *pSOCache )
|
||||
: m_steamID( steamID ),
|
||||
m_pSOCache( pSOCache ),
|
||||
m_bIsShuttingDown( false ),
|
||||
m_osType( k_eOSUnknown ),
|
||||
m_bIsTestSession( false ),
|
||||
m_bIsSecure( false ),
|
||||
m_unIPPublic( 0 ),
|
||||
m_flLatitude( 0.0f ),
|
||||
m_flLongitude( 0.0f ) ,
|
||||
m_haveGeoLocation( false ),
|
||||
m_bInitialized( false ),
|
||||
m_rtLastMessageReceived( 0 )
|
||||
{
|
||||
m_jtLastMessageReceived.SetLTime( 0 );
|
||||
m_jtTimeSentPing.SetLTime( 0 );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CGCSession::~CGCSession()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Checks the message against rate limiting. Returns true if we should
|
||||
// drop the message. False otherwise. This default behavior is a very basic
|
||||
// n messages / user / second rate limiting that's only meant to stop the
|
||||
// worse abuses
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CGCSession::BRateLimitMessage( MsgType_t unMsgType )
|
||||
{
|
||||
unMsgType &= ~k_EMsgProtoBufFlag;
|
||||
if ( max_user_messages_per_second.GetInt() <= 0 )
|
||||
return false;
|
||||
|
||||
RTime32 rtCur = CRTime::RTime32TimeCur();
|
||||
m_jtLastMessageReceived.SetToJobTime();
|
||||
if ( m_rtLastMessageReceived != rtCur )
|
||||
{
|
||||
m_rtLastMessageReceived = rtCur;
|
||||
m_unMessagesRecievedThisSecond = 0;
|
||||
}
|
||||
|
||||
m_unMessagesRecievedThisSecond++;
|
||||
if ( m_unMessagesRecievedThisSecond > (uint32)max_user_messages_per_second.GetInt() )
|
||||
{
|
||||
//log this message
|
||||
g_RateLimitTracker.TrackRateLimitedMsg( GetSteamID(), unMsgType );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: The run function is called on each session (user and gameserver)
|
||||
// approximately every k_nUserSessionRunInterval microseconds (or
|
||||
// k_nGSSessionRunInterval for GS Sessions)
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGCSession::Run()
|
||||
{
|
||||
// These cached subscription messages are very expensive and only needed for a short period of time
|
||||
// If we're hitting the run loop, it's been around long enough
|
||||
GetSOCache()->ClearCachedSubscriptionMessage();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CGCSession::GetGeoLocation( float &latitude, float &longittude ) const
|
||||
{
|
||||
latitude = m_flLatitude;
|
||||
longittude = m_flLongitude;
|
||||
|
||||
return m_haveGeoLocation;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGCSession::SetGeoLocation( float latitude, float longittude )
|
||||
{
|
||||
m_flLatitude = latitude;
|
||||
m_flLongitude = longittude;
|
||||
m_haveGeoLocation = true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Claims all the memory for the session object
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
void CGCSession::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
VALIDATE_SCOPE();
|
||||
}
|
||||
#endif // DBGFLAG_VALIDATE
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CGCUserSession::~CGCUserSession()
|
||||
{
|
||||
if ( m_steamIDGS.BGameServerAccount() )
|
||||
{
|
||||
EmitError( SPEW_GC, "Destroying user %s while still connected to server %s\n", GetSteamID().Render(), GetSteamIDGS().Render() );
|
||||
}
|
||||
}
|
||||
|
||||
bool CGCUserSession::BInit()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Sets the session's game server to the given SteamID. This will
|
||||
// cause the session to leave the current server it's on, it any
|
||||
// Returns: True if the user's session was added to the GS's session.
|
||||
// False if the session could not be found or if the user was already
|
||||
// on the server.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CGCUserSession::BSetServer( const CSteamID &steamIDGS )
|
||||
{
|
||||
if ( steamIDGS == m_steamIDGS )
|
||||
return false;
|
||||
|
||||
BLeaveServer();
|
||||
|
||||
if( steamIDGS.IsValid() )
|
||||
{
|
||||
CGCGSSession *pGSSession = GGCBase()->FindGSSession( steamIDGS );
|
||||
if ( !pGSSession )
|
||||
{
|
||||
EmitError( SPEW_GC, "User %s attempting to join server %s which has no session\n", GetSteamID().Render(), steamIDGS.Render() );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !pGSSession->BAddUser( GetSteamID() ) )
|
||||
{
|
||||
EmitWarning( SPEW_GC, SPEW_ALWAYS, "Server %s already had user %s in its user list\n", steamIDGS.Render(), GetSteamID().Render() );
|
||||
// Fall through
|
||||
}
|
||||
}
|
||||
|
||||
m_steamIDGS = steamIDGS;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Removes the session from the given game server
|
||||
// Returns: True if the user's session was removed from the GS's session.
|
||||
// False if the session could not be found or if the user was not found
|
||||
// on the server.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CGCUserSession::BLeaveServer()
|
||||
{
|
||||
if( m_steamIDGS.IsValid() )
|
||||
{
|
||||
// Remember the last server we were connected to
|
||||
m_steamIDGSPrev = m_steamIDGS;
|
||||
|
||||
CGCGSSession *pGSSession = GGCBase()->FindGSSession( m_steamIDGS );
|
||||
if ( pGSSession )
|
||||
{
|
||||
pGSSession->BRemoveUser( GetSteamID() );
|
||||
}
|
||||
}
|
||||
|
||||
m_steamIDGS = CSteamID();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Dumps useful information about this session
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGCUserSession::Dump( bool bFull ) const
|
||||
{
|
||||
// this is ifdef'd out in Steam because GCSDK can't depend on steamid.cpp
|
||||
#ifndef STEAM
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "User Session %s (%s)\n", GetSteamID().Render(), BIsShuttingDown() ? "SHUTTING DOWN" : "Active" );
|
||||
CJob *pJob = GGCBase()->PJobHoldingLock( GetSteamID() );
|
||||
if( pJob )
|
||||
{
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\t LOCKED BY: %s\n", pJob->GetName() );
|
||||
}
|
||||
if( bFull && GetSOCache() )
|
||||
GetSOCache()->Dump();
|
||||
if( GetSteamIDGS().BGameServerAccount() )
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\tGameserver: %s\n", GetSteamIDGS().Render() );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\tOS: %d Secure: %d\n", GetOSType(), IsSecure() ? 1 : 0 );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CGCGSSession::CGCGSSession( const CSteamID & steamID, CGCSharedObjectCache *pCache, uint32 unServerAddr, uint16 usServerPort )
|
||||
: CGCSession( steamID, pCache ), m_unServerAddr( unServerAddr ), m_usServerPort( usServerPort )
|
||||
{
|
||||
if ( gs_session_assert_valid_addr_and_port.GetBool() )
|
||||
{
|
||||
Assert( unServerAddr );
|
||||
Assert( usServerPort );
|
||||
}
|
||||
|
||||
// Default our public IP to be the same as our IP address
|
||||
m_unIPPublic = unServerAddr;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CGCGSSession::~CGCGSSession()
|
||||
{
|
||||
if ( m_vecUsers.Count() > 0 )
|
||||
{
|
||||
EmitError( SPEW_GC, "Destroying game server %s while %d users are still connected\n", GetSteamID().Render(), m_vecUsers.Count() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Adds a user to the list of users active on the game server
|
||||
// Returns: True if the user was added, false if the user was already on this
|
||||
// server.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CGCGSSession::BAddUser( const CSteamID &steamIDUser )
|
||||
{
|
||||
if( m_vecUsers.HasElement( steamIDUser ) )
|
||||
return false;
|
||||
|
||||
PreAddUser( steamIDUser );
|
||||
m_vecUsers.AddToTail( steamIDUser );
|
||||
PostAddUser( steamIDUser );
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Called if our IP / port changes after session is started
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGCGSSession::SetIPAndPort( uint32 unServerAddr, uint16 usServerPort )
|
||||
{
|
||||
// If we didn't have an override for the public IP, then also
|
||||
// update the public IP.
|
||||
//
|
||||
// !KLUDGE! This is gross for two reasons:
|
||||
// - First, do we really need two different fields?
|
||||
// - Second, why can the IP change after the session is created?
|
||||
// Shouldn't we force the session to be destroyed and recreated?
|
||||
// It cannot *really* be the same "session", can it?
|
||||
if ( m_unIPPublic == m_unServerAddr )
|
||||
m_unIPPublic = unServerAddr;
|
||||
m_unServerAddr = unServerAddr;
|
||||
m_usServerPort = usServerPort;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Removes a user from the list of users active on the game server
|
||||
// Returns: True if the user was added, false if the user was not already on
|
||||
// this server.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CGCGSSession::BRemoveUser( const CSteamID &steamIDUser )
|
||||
{
|
||||
int nIndex = m_vecUsers.Find( steamIDUser );
|
||||
if ( !m_vecUsers.IsValidIndex( nIndex ) )
|
||||
return false;
|
||||
|
||||
PreRemoveUser( steamIDUser );
|
||||
m_vecUsers.Remove( nIndex );
|
||||
PostRemoveUser( steamIDUser );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Removes all users from the list of game server users.
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGCGSSession::RemoveAllUsers()
|
||||
{
|
||||
if ( 0 == m_vecUsers.Count() )
|
||||
return;
|
||||
|
||||
PreRemoveAllUsers();
|
||||
|
||||
// Iterate all the users and tell them to leave this server.
|
||||
// Using back because the users will remove themselves from
|
||||
// this list during this function
|
||||
FOR_EACH_VEC_BACK( m_vecUsers, i )
|
||||
{
|
||||
CGCUserSession *pUserSession = GGCBase()->FindUserSession( m_vecUsers[i] );
|
||||
if ( pUserSession )
|
||||
{
|
||||
pUserSession->BLeaveServer();
|
||||
}
|
||||
}
|
||||
|
||||
// Catch anyone we don't have a session for anymore
|
||||
m_vecUsers.RemoveAll();
|
||||
|
||||
PostRemoveAllUsers();
|
||||
}
|
||||
|
||||
#define iptod(x) ((x)>>24&0xff), ((x)>>16&0xff), ((x)>>8&0xff), ((x)&0xff)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Dumps useful information about this session
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGCGSSession::Dump( bool bFull ) const
|
||||
{
|
||||
// this is ifdef'd out in Steam because GCSDK can't depend on steamid.cpp
|
||||
#ifndef STEAM
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "GS Session %s\n", GetSteamID().Render() );
|
||||
CJob *pJob = GGCBase()->PJobHoldingLock( GetSteamID() );
|
||||
if( pJob )
|
||||
{
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\t LOCKED BY: %s\n", pJob->GetName() );
|
||||
}
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\t%d users:\n", m_vecUsers.Count() );
|
||||
FOR_EACH_VEC( m_vecUsers, nUser )
|
||||
{
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\t\t%s\n", m_vecUsers[nUser].Render() );
|
||||
}
|
||||
if( GetSOCache() )
|
||||
{
|
||||
if ( bFull )
|
||||
{
|
||||
GetSOCache()->Dump();
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\t SO Cache Version: %llu\n", GetSOCache()->GetVersion() );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Claims all the memory for the session object
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
void CGCGSSession::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
CGCSession::Validate( validator, pchName);
|
||||
|
||||
VALIDATE_SCOPE();
|
||||
|
||||
ValidateObj( m_vecUsers );
|
||||
}
|
||||
#endif // DBGFLAG_VALIDATE
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Client says it needs the SO Cache
|
||||
// Input : pNetPacket - received message
|
||||
//-----------------------------------------------------------------------------
|
||||
class CGCCacheSubscriptionRefresh: public CGCJob
|
||||
{
|
||||
public:
|
||||
CGCCacheSubscriptionRefresh( CGCBase *pGC ) : CGCJob( pGC ) { }
|
||||
bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket );
|
||||
};
|
||||
|
||||
bool CGCCacheSubscriptionRefresh::BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
|
||||
{
|
||||
CProtoBufMsg<CMsgSOCacheSubscriptionRefresh> msg( pNetPacket );
|
||||
|
||||
CSteamID steamID( msg.Hdr().client_steam_id() );
|
||||
|
||||
CSteamID steamIDCacheOwner( msg.Body().owner() );
|
||||
CGCSharedObjectCache *pCache = m_pGC->FindSOCache( steamIDCacheOwner );
|
||||
if ( pCache == NULL || !pCache->BIsSubscribed( steamID ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pCache->SendSubscriberMessage( steamID );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
GC_REG_JOB( CGCBase, CGCCacheSubscriptionRefresh, "CGCCacheSubscriptionRefresh", k_ESOMsg_CacheSubscriptionRefresh, k_EServerTypeGC );
|
||||
|
||||
} // namespace GCSDK
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "tier0/memdbgoff.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
IMPLEMENT_CLASS_MEMPOOL( CGCSQLQuery, 1000, UTLMEMORYPOOL_GROW_SLOW );
|
||||
IMPLEMENT_CLASS_MEMPOOL( CGCSQLQueryGroup, 1000, UTLMEMORYPOOL_GROW_SLOW );
|
||||
}
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Constructor.
|
||||
//----------------------------------------------------------------------------
|
||||
// create queries on the heap with Alloc
|
||||
CGCSQLQuery::CGCSQLQuery()
|
||||
{
|
||||
m_pBufParams = GetBufferPool().GetBuffer();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Destructor.
|
||||
//----------------------------------------------------------------------------
|
||||
CGCSQLQuery::~CGCSQLQuery()
|
||||
{
|
||||
// free all the data
|
||||
ClearParams();
|
||||
GetBufferPool().ReturnBuffer( m_pBufParams );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Gets a singleton buffer pool for bind params
|
||||
//----------------------------------------------------------------------------
|
||||
static GCConVar sql_bind_param_max_pool_size_mb( "sql_bind_param_max_pool_size_mb", "10", "Maximum size in bytes of the SQL Bind Param buffer pool" );
|
||||
static GCConVar sql_bind_param_init_buffer_size( "sql_bind_param_init_buffer_size", "16384", "Initial buffer size for buffers in the SQL Bind Param buffer pool" );
|
||||
/*static*/ CBufferPool &CGCSQLQuery::GetBufferPool()
|
||||
{
|
||||
static CBufferPool s_bufferPool( "SQL bind params", sql_bind_param_max_pool_size_mb, sql_bind_param_init_buffer_size );
|
||||
return s_bufferPool;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Allocates a bit of memory for the parameter and adds it to the list
|
||||
//----------------------------------------------------------------------------
|
||||
void CGCSQLQuery::AddBindParamRaw( EGCSQLType eType, const byte *pubData, uint32 cubData )
|
||||
{
|
||||
Assert( m_vecParams.Count() < k_cMaxBindParams );
|
||||
if( m_vecParams.Count() >= k_cMaxBindParams )
|
||||
return;
|
||||
|
||||
GCSQLBindParam_t ¶m = m_vecParams[ m_vecParams.AddToTail() ];
|
||||
param.m_eType = eType;
|
||||
param.m_cubData = cubData;
|
||||
|
||||
if ( cubData )
|
||||
{
|
||||
param.m_nOffset = m_pBufParams->TellPut();
|
||||
m_pBufParams->Put( pubData, cubData );
|
||||
}
|
||||
else
|
||||
{
|
||||
param.m_nOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: frees all the bind params
|
||||
//----------------------------------------------------------------------------
|
||||
void CGCSQLQuery::ClearParams()
|
||||
{
|
||||
m_vecParams.RemoveAll();
|
||||
m_pBufParams->Clear();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Constructor.
|
||||
//----------------------------------------------------------------------------
|
||||
// create queries on the heap with Alloc
|
||||
CGCSQLQueryGroup::CGCSQLQueryGroup()
|
||||
: m_pResults( NULL )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Sets the result pointer
|
||||
//----------------------------------------------------------------------------
|
||||
CGCSQLQueryGroup::~CGCSQLQueryGroup()
|
||||
{
|
||||
// free all the data
|
||||
Clear();
|
||||
|
||||
if( m_pResults )
|
||||
m_pResults->Destroy();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Adds a new query to the group
|
||||
//----------------------------------------------------------------------------
|
||||
void CGCSQLQueryGroup::AddQuery( CGCSQLQuery *pQuery )
|
||||
{
|
||||
m_vecQueries.AddToTail( pQuery );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Sets the name (file and line) for the group
|
||||
//----------------------------------------------------------------------------
|
||||
void CGCSQLQueryGroup::SetName( const char *sName )
|
||||
{
|
||||
m_sName = sName;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: clears all the queries in the query group and resets its name
|
||||
//----------------------------------------------------------------------------
|
||||
void CGCSQLQueryGroup::Clear()
|
||||
{
|
||||
m_vecQueries.PurgeAndDeleteElements();
|
||||
m_sName = "";
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Sets the result pointer
|
||||
//----------------------------------------------------------------------------
|
||||
void CGCSQLQueryGroup::SetResults( IGCSQLResultSetList *pResults )
|
||||
{
|
||||
if( m_pResults )
|
||||
m_pResults->Destroy();
|
||||
m_pResults = pResults;
|
||||
}
|
||||
|
||||
} // namespace GCSDK
|
||||
@@ -0,0 +1,690 @@
|
||||
//========= Copyright (c), Valve LLC, All rights reserved. ============
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "gcsystemaccess.h"
|
||||
#include "gcjob.h"
|
||||
#include "gcsdk/gcreportprinter.h"
|
||||
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
|
||||
static GCConVar gcaccess_enable( "gcaccess_enable", "1", "Kill switch that disables all gcaccess tracking and systems" );
|
||||
|
||||
DECLARE_GC_EMIT_GROUP( g_EGAccess, access );
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------------------------------------
|
||||
GCAccessSystem_t GenerateUniqueGCAccessID()
|
||||
{
|
||||
static GCAccessSystem_t s_nID = 0;
|
||||
return s_nID++;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------------------------------------
|
||||
// CGCAccessSystem
|
||||
//-----------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
//a GCAccess system that has been registered
|
||||
class CGCAccessSystem
|
||||
{
|
||||
public:
|
||||
//the name of the system for display
|
||||
CUtlString m_sName;
|
||||
//the unique ID for this system
|
||||
GCAccessSystem_t m_nID;
|
||||
//which actions should be enabled or disabled for this system in particular
|
||||
uint32 m_nActions;
|
||||
uint32 m_nSuppressActions;
|
||||
|
||||
struct AccessStats_t
|
||||
{
|
||||
AccessStats_t();
|
||||
void Add( const AccessStats_t& rhs );
|
||||
//how many raw accesses have we had?
|
||||
uint32 m_nNumValid;
|
||||
//how many failed because the context didn't have rights?
|
||||
uint32 m_nNumFail;
|
||||
//how many failed because the associated ID wasn't valid?
|
||||
uint32 m_nNumFailID;
|
||||
};
|
||||
|
||||
struct TrackedJob_t
|
||||
{
|
||||
CUtlString m_sContext;
|
||||
AccessStats_t m_Stats;
|
||||
};
|
||||
|
||||
//which jobs have violated this system access rights
|
||||
CUtlHashMapLarge< uintp, TrackedJob_t > m_Jobs;
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------------------------------------
|
||||
// CGCAccessContext
|
||||
//-----------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
CGCAccessContext::CGCAccessContext() :
|
||||
m_nActions( 0 ),
|
||||
m_nSuppressActions( 0 )
|
||||
{
|
||||
}
|
||||
|
||||
void CGCAccessContext::Init( const char* pszName, uint32 nActions, uint32 nSuppressActions )
|
||||
{
|
||||
m_sName = pszName;
|
||||
m_nActions = nActions;
|
||||
m_nSuppressActions = nSuppressActions;
|
||||
}
|
||||
|
||||
void CGCAccessContext::AddSystem( GCAccessSystem_t nSystem )
|
||||
{
|
||||
m_nSystems.InsertIfNotFound( nSystem );
|
||||
}
|
||||
|
||||
bool CGCAccessContext::HasSystem( GCAccessSystem_t system ) const
|
||||
{
|
||||
return ( m_nSystems.Find( system ) != m_nSystems.InvalidIndex() );
|
||||
}
|
||||
|
||||
bool CGCAccessContext::IsSubsetOf( const CGCAccessContext& context ) const
|
||||
{
|
||||
FOR_EACH_VEC( m_nSystems, nCurrSystem )
|
||||
{
|
||||
if( !context.HasSystem( m_nSystems[ nCurrSystem ] ) )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGCAccessContext::AddSystemsFrom( const CGCAccessContext& context )
|
||||
{
|
||||
FOR_EACH_VEC( context.m_nSystems, nCurrSystem )
|
||||
{
|
||||
AddSystem( context.m_nSystems[ nCurrSystem ] );
|
||||
}
|
||||
}
|
||||
|
||||
void CGCAccessContext::SetAction( EGCAccessAction eAction, bool bSet )
|
||||
{
|
||||
if( bSet )
|
||||
m_nActions |= eAction;
|
||||
else
|
||||
m_nActions &= ~( uint32 )eAction;
|
||||
}
|
||||
|
||||
void CGCAccessContext::SetSuppressAction( EGCAccessAction eAction, bool bSet )
|
||||
{
|
||||
if( bSet )
|
||||
m_nSuppressActions |= eAction;
|
||||
else
|
||||
m_nSuppressActions &= ~( uint32 )eAction;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------------------------------------
|
||||
// CGCAccess
|
||||
//-----------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
static CGCAccess g_GCAccess;
|
||||
CGCAccess& GGCAccess()
|
||||
{
|
||||
return g_GCAccess;
|
||||
}
|
||||
|
||||
CGCAccessSystem::AccessStats_t::AccessStats_t()
|
||||
: m_nNumFail( 0 )
|
||||
, m_nNumFailID( 0 )
|
||||
, m_nNumValid( 0 )
|
||||
{}
|
||||
|
||||
void CGCAccessSystem::AccessStats_t::Add( const AccessStats_t& rhs )
|
||||
{
|
||||
m_nNumFail += rhs.m_nNumFail;
|
||||
m_nNumFailID += rhs.m_nNumFailID;
|
||||
m_nNumValid += rhs.m_nNumValid;
|
||||
}
|
||||
|
||||
CGCAccess::CGCAccess() :
|
||||
m_nActions( GCAccessAction_TrackSuccess | GCAccessAction_TrackFail| GCAccessAction_ReturnFail ),
|
||||
m_nSuppressActions( 0 )
|
||||
{
|
||||
m_GlobalContext.Init( "Global" );
|
||||
}
|
||||
|
||||
void CGCAccess::RegisterSystem( const char* pszName, GCAccessSystem_t nID, uint32 nActions, uint32 nSupressActions )
|
||||
{
|
||||
//make sure that we don't have a conflict
|
||||
int nIndex = m_Systems.Find( nID );
|
||||
if( nIndex != m_Systems.InvalidIndex() )
|
||||
{
|
||||
// !FIXME!
|
||||
// DOTAMERGE AssertMsg varargs
|
||||
AssertMsg3( false, "Multiple systems conflciting in Register System: ID %d, original: %s, new %s", nID, m_Systems[ nIndex ]->m_sName.String(), pszName );
|
||||
return;
|
||||
}
|
||||
|
||||
CGCAccessSystem* pSystem = new CGCAccessSystem;
|
||||
pSystem->m_sName = pszName;
|
||||
pSystem->m_nID = nID;
|
||||
pSystem->m_nActions = nActions;
|
||||
pSystem->m_nSuppressActions = nSupressActions;
|
||||
m_Systems.Insert( nID, pSystem );
|
||||
}
|
||||
|
||||
bool CGCAccess::ValidateAccess( GCAccessSystem_t nSystem )
|
||||
{
|
||||
//global kill switch
|
||||
if( !gcaccess_enable.GetBool() )
|
||||
return true;
|
||||
|
||||
return InternalValidateAccess( nSystem, CSteamID(), CSteamID() );
|
||||
}
|
||||
|
||||
bool CGCAccess::ValidateSteamIDAccess( GCAccessSystem_t nSystem, CSteamID steamID )
|
||||
{
|
||||
//global kill switch
|
||||
if( !gcaccess_enable.GetBool() )
|
||||
return true;
|
||||
|
||||
CSteamID expectedID = ( g_pJobCur ) ? g_pJobCur->SOVALIDATE_GetSteamID() : steamID;
|
||||
return InternalValidateAccess( nSystem, steamID, expectedID );
|
||||
}
|
||||
|
||||
bool CGCAccess::InternalValidateAccess( GCAccessSystem_t nSystem, CSteamID steamID, CSteamID expectedID )
|
||||
{
|
||||
//see if tracking for this system is disabled. This list is almost always empty, so just use a linear search for now.
|
||||
FOR_EACH_VEC( m_SuppressAccess, nAction )
|
||||
{
|
||||
if( m_SuppressAccess[ nAction ].m_nSystem == nSystem )
|
||||
return true;
|
||||
}
|
||||
|
||||
//assume the global context
|
||||
const CGCAccessContext* pContext = &m_GlobalContext;
|
||||
const char* pszJobName = "[global]";
|
||||
|
||||
//if we have a job, use it's context
|
||||
if( g_pJobCur )
|
||||
{
|
||||
const CGCJob* pJob = static_cast< const CGCJob* >( g_pJobCur );
|
||||
pszJobName = pJob->GetName();
|
||||
if( pJob->GetContext() )
|
||||
pContext = pJob->GetContext();
|
||||
}
|
||||
|
||||
//is this a valid system to access
|
||||
bool bValidSteamID = ( steamID == expectedID );
|
||||
bool bValidSystem = pContext->HasSystem( nSystem );
|
||||
bool bValidAccess = ( bValidSystem && bValidSteamID );
|
||||
|
||||
//determine the actions we want to do for tracking
|
||||
uint32 nActions = pContext->GetActions() | m_nActions;
|
||||
uint32 nSuppressActions = pContext->GetSuppressActions() | m_nSuppressActions;
|
||||
|
||||
//look up the system that we are accessing
|
||||
CGCAccessSystem* pSystem = NULL;
|
||||
int nSystemIndex = m_Systems.Find( nSystem );
|
||||
if( nSystemIndex == m_Systems.InvalidIndex() )
|
||||
{
|
||||
// !FIXME! DOTAMERGE
|
||||
// AssertMsg varargs
|
||||
AssertMsg1( false, "Error: Tracking a system that has not been registered. Make sure to register system %u", nSystem );
|
||||
}
|
||||
else
|
||||
{
|
||||
//make sure that we have a stat entry for this job
|
||||
pSystem = m_Systems[ nSystemIndex ];
|
||||
nActions |= pSystem->m_nActions;
|
||||
nSuppressActions |= pSystem->m_nSuppressActions;
|
||||
}
|
||||
|
||||
//remove any suppressed actions
|
||||
nActions &= ~nSuppressActions;
|
||||
|
||||
//see if we need to track this access
|
||||
bool bTrackSuccess = ( nActions & GCAccessAction_TrackSuccess ) && bValidAccess;
|
||||
bool bTrackFail = ( nActions & GCAccessAction_TrackFail ) && !bValidAccess;
|
||||
if( ( bTrackSuccess || bTrackFail ) && pSystem )
|
||||
{
|
||||
//make sure that we have a stat entry for this job
|
||||
int nJobIndex = pSystem->m_Jobs.Find( ( uintp )pszJobName );
|
||||
if( nJobIndex == pSystem->m_Jobs.InvalidIndex() )
|
||||
{
|
||||
nJobIndex = pSystem->m_Jobs.Insert( ( uintp )pszJobName );
|
||||
pSystem->m_Jobs[ nJobIndex ].m_sContext = pContext->GetName();
|
||||
}
|
||||
|
||||
//update our stats based upon what was valid and what wasn't
|
||||
CGCAccessSystem::AccessStats_t& stats = pSystem->m_Jobs[ nJobIndex ].m_Stats;
|
||||
if( bTrackSuccess )
|
||||
stats.m_nNumValid++;
|
||||
|
||||
if( bTrackFail )
|
||||
{
|
||||
if( !bValidSteamID )
|
||||
stats.m_nNumFailID++;
|
||||
if( !bValidSystem )
|
||||
stats.m_nNumFail++;
|
||||
}
|
||||
}
|
||||
|
||||
//see if this is a single access we want to try and track
|
||||
FOR_EACH_VEC( m_SingleAsserts, nCurrAssert )
|
||||
{
|
||||
SingleAssert_t* pAssert = m_SingleAsserts[ nCurrAssert ];
|
||||
if( nSystem == pAssert->m_System )
|
||||
{
|
||||
if( V_stricmp( ( pAssert->m_bContext ) ? pContext->GetName() : pszJobName, pAssert->m_sContextOrJob ) == 0 )
|
||||
{
|
||||
//log this assert
|
||||
{
|
||||
// !FIXME! DOTAMERGE
|
||||
//CGCInterface::CDisableAssertRateLimit disableAssertLimit;
|
||||
|
||||
// !FIXME! DOTAMERGE
|
||||
// AssertMsg varargs
|
||||
AssertMsg3( false, "GCSystemAccess Caught %s (context %s) calling into %s", pszJobName, pContext->GetName(), pSystem ? pSystem->m_sName.String() : "unknown" );
|
||||
}
|
||||
|
||||
//and clear it
|
||||
delete pAssert;
|
||||
m_SingleAsserts.FastRemove( nCurrAssert );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//at this point, if it is a valid access, just bail
|
||||
if( bValidAccess )
|
||||
return true;
|
||||
|
||||
//otherwise, handle the failure case
|
||||
if( nActions & ( GCAccessAction_Msg | GCAccessAction_Assert ) )
|
||||
{
|
||||
int nSystemIndex = m_Systems.Find( nSystem );
|
||||
const char* pszSystem = ( nSystemIndex != m_Systems.InvalidIndex() ) ? m_Systems[ nSystemIndex ]->m_sName.String() : "<unregistered>";
|
||||
|
||||
//display a message based upon if it was a system or ID violation
|
||||
CFmtStr1024 szMsg;
|
||||
if( !bValidSystem )
|
||||
{
|
||||
szMsg.sprintf( "Job %s Accessed invalid system %s (%u) while in context %s\n", pszJobName, pszSystem, nSystem, pContext->GetName() );
|
||||
}
|
||||
else
|
||||
{
|
||||
szMsg.sprintf( "Job %s Accessed invalid steam ID %s but expected %s in system %s (%u) while in context %s\n", pszJobName, steamID.RenderLink(), expectedID.RenderLink(), pszSystem, nSystem, pContext->GetName() );
|
||||
}
|
||||
|
||||
if( nActions & GCAccessAction_Msg )
|
||||
EG_MSG( g_EGAccess, "%s", szMsg.String() );
|
||||
if( nActions & GCAccessAction_Assert )
|
||||
{
|
||||
// !FIXME! DOTAMERGE
|
||||
// AssertMsg varargs
|
||||
AssertMsg1( false, "%s", szMsg.String() );
|
||||
}
|
||||
}
|
||||
|
||||
return !( nActions & GCAccessAction_ReturnFail );
|
||||
}
|
||||
|
||||
void CGCAccess::SuppressAccess( GCAccessSystem_t nSystem, bool bEnable )
|
||||
{
|
||||
//see if it is an existing item already suppressed that we need to modify the ref count of
|
||||
FOR_EACH_VEC( m_SuppressAccess, nAccess )
|
||||
{
|
||||
if( m_SuppressAccess[ nAccess ].m_nSystem == nSystem )
|
||||
{
|
||||
if( bEnable )
|
||||
{
|
||||
//to enable, we want to decrease the disable count, or remove if we are fully re-enabled
|
||||
if( m_SuppressAccess[ nAccess ].m_nCount <= 1 )
|
||||
m_SuppressAccess.FastRemove( nAccess );
|
||||
else
|
||||
m_SuppressAccess[ nAccess ].m_nCount--;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_SuppressAccess[ nAccess ].m_nCount++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//only add it to the list if we are disabling
|
||||
if( !bEnable )
|
||||
{
|
||||
SuppressAccess_t access;
|
||||
access.m_nSystem = nSystem;
|
||||
access.m_nCount = 1;
|
||||
m_SuppressAccess.AddToTail( access );
|
||||
}
|
||||
}
|
||||
|
||||
void CGCAccess::ClearSystemStats()
|
||||
{
|
||||
FOR_EACH_MAP_FAST( m_Systems, nSystem )
|
||||
{
|
||||
m_Systems[ nSystem ]->m_Jobs.Purge();
|
||||
}
|
||||
}
|
||||
|
||||
bool CGCAccess::CatchSingleAssert( const char* pszSystem, bool bContext, const char* pszContextOrJob )
|
||||
{
|
||||
//find the system we are referencing so we don't add invalid breakpoints if we can avoid it
|
||||
GCAccessSystem_t nSystemID = ( GCAccessSystem_t )-1;
|
||||
FOR_EACH_MAP_FAST( m_Systems, nSystem )
|
||||
{
|
||||
if( V_stricmp( m_Systems[ nSystem ]->m_sName, pszSystem ) == 0 )
|
||||
{
|
||||
nSystemID = m_Systems.Key( nSystem );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( nSystemID == ( GCAccessSystem_t )-1 )
|
||||
return false;
|
||||
|
||||
SingleAssert_t* pAssert = new SingleAssert_t;
|
||||
pAssert->m_System = nSystemID;
|
||||
pAssert->m_bContext = bContext;
|
||||
pAssert->m_sContextOrJob = pszContextOrJob;
|
||||
m_SingleAsserts.AddToTail( pAssert );
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGCAccess::ClearSingleAsserts()
|
||||
{
|
||||
m_SingleAsserts.PurgeAndDeleteElements();
|
||||
}
|
||||
|
||||
|
||||
//given a display type and a stats object, determines if it meets the criteria
|
||||
static bool ShouldDisplayStats( CGCAccess::EDisplay eDisplay, const CGCAccessSystem::AccessStats_t& stats )
|
||||
{
|
||||
if( eDisplay == CGCAccess::eDisplay_Referenced )
|
||||
return ( stats.m_nNumFail + stats.m_nNumFailID + stats.m_nNumValid ) > 0;
|
||||
if( eDisplay == CGCAccess::eDisplay_Violations )
|
||||
return ( stats.m_nNumFail + stats.m_nNumFailID ) > 0;
|
||||
if( eDisplay == CGCAccess::eDisplay_IDViolations )
|
||||
return stats.m_nNumFailID > 0;
|
||||
|
||||
//unknown, so just assume all
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void CGCAccess::ReportJobs( const char* pszContext, EDisplay eDisplay ) const
|
||||
{
|
||||
//collect all of the job stats into a single summary table
|
||||
CUtlHashMapLarge< uintp, CGCAccessSystem::TrackedJob_t > jobs;
|
||||
FOR_EACH_MAP_FAST( m_Systems, nSystem )
|
||||
{
|
||||
const CGCAccessSystem* pSystem = m_Systems[ nSystem ];
|
||||
FOR_EACH_MAP_FAST( pSystem->m_Jobs, nJob )
|
||||
{
|
||||
const CGCAccessSystem::TrackedJob_t& job = pSystem->m_Jobs[ nJob ];
|
||||
//skip any contexts we don't care about
|
||||
if( pszContext && ( V_stricmp( pszContext, job.m_sContext ) != 0 ) )
|
||||
continue;
|
||||
if( !ShouldDisplayStats( eDisplay, job.m_Stats ) )
|
||||
continue;
|
||||
|
||||
int nIndex = jobs.Find( pSystem->m_Jobs.Key( nJob ) );
|
||||
if( nIndex == jobs.InvalidIndex() )
|
||||
{
|
||||
nIndex = jobs.Insert( pSystem->m_Jobs.Key( nJob ) );
|
||||
jobs[ nIndex ].m_sContext = job.m_sContext;
|
||||
}
|
||||
|
||||
jobs[ nIndex ].m_Stats.Add( job.m_Stats );
|
||||
}
|
||||
}
|
||||
|
||||
CGCReportPrinter rp;
|
||||
rp.AddStringColumn( "Job" );
|
||||
rp.AddStringColumn( "Context" );
|
||||
rp.AddIntColumn( "Valid", CGCReportPrinter::eSummary_Total );
|
||||
rp.AddIntColumn( "Invalid", CGCReportPrinter::eSummary_Total );
|
||||
rp.AddIntColumn( "InvalidID", CGCReportPrinter::eSummary_Total );
|
||||
|
||||
FOR_EACH_MAP_FAST( jobs, nJob )
|
||||
{
|
||||
rp.StrValue( ( const char* )jobs.Key( nJob ), CFmtStr( "gcaccess_dump_job \"%s\" %d", ( const char* )jobs.Key( nJob ), eDisplay ).String() );
|
||||
rp.StrValue( jobs[ nJob ].m_sContext, CFmtStr( "gcaccess_dump_context \"%s\" %d", jobs[ nJob ].m_sContext.String(), eDisplay ).String() );
|
||||
rp.IntValue( jobs[ nJob ].m_Stats.m_nNumValid );
|
||||
rp.IntValue( jobs[ nJob ].m_Stats.m_nNumFail );
|
||||
rp.IntValue( jobs[ nJob ].m_Stats.m_nNumFailID );
|
||||
rp.CommitRow();
|
||||
}
|
||||
|
||||
rp.SortReport( "Job", false );
|
||||
rp.PrintReport( SPEW_CONSOLE );
|
||||
}
|
||||
|
||||
void CGCAccess::ReportSystems( const char* pszContext, EDisplay eDisplay ) const
|
||||
{
|
||||
CGCReportPrinter rp;
|
||||
rp.AddStringColumn( "System" );
|
||||
rp.AddIntColumn( "ID", CGCReportPrinter::eSummary_None );
|
||||
rp.AddIntColumn( "Jobs", CGCReportPrinter::eSummary_None );
|
||||
rp.AddIntColumn( "Valid", CGCReportPrinter::eSummary_Total );
|
||||
rp.AddIntColumn( "Invalid", CGCReportPrinter::eSummary_Total );
|
||||
rp.AddIntColumn( "InvalidID", CGCReportPrinter::eSummary_Total );
|
||||
|
||||
FOR_EACH_MAP_FAST( m_Systems, nSystem )
|
||||
{
|
||||
const CGCAccessSystem* pSystem = m_Systems[ nSystem ];
|
||||
CGCAccessSystem::AccessStats_t stats;
|
||||
FOR_EACH_MAP_FAST( pSystem->m_Jobs, nJob )
|
||||
{
|
||||
const CGCAccessSystem::TrackedJob_t& job = pSystem->m_Jobs[ nJob ];
|
||||
//skip any contexts we don't care about
|
||||
if( pszContext && ( V_stricmp( pszContext, job.m_sContext ) != 0 ) )
|
||||
continue;
|
||||
|
||||
stats.Add( job.m_Stats );
|
||||
}
|
||||
|
||||
if( !ShouldDisplayStats( eDisplay, stats ) )
|
||||
continue;
|
||||
|
||||
rp.StrValue( pSystem->m_sName, CFmtStr( "gcaccess_dump_system \"%s\" %d", pSystem->m_sName.String(), eDisplay ).String() );
|
||||
rp.IntValue( pSystem->m_nID );
|
||||
rp.IntValue( pSystem->m_Jobs.Count() );
|
||||
rp.IntValue( stats.m_nNumValid );
|
||||
rp.IntValue( stats.m_nNumFail );
|
||||
rp.IntValue( stats.m_nNumFailID );
|
||||
rp.CommitRow();
|
||||
}
|
||||
|
||||
rp.SortReport( "System", false );
|
||||
rp.PrintReport( SPEW_CONSOLE );
|
||||
}
|
||||
|
||||
void CGCAccess::FullReport( const char* pszSystemFilter, const char* pszContextFilter, const char* pszJobFilter, EDisplay eDisplay ) const
|
||||
{
|
||||
CGCReportPrinter rp;
|
||||
rp.AddStringColumn( "Job" );
|
||||
rp.AddStringColumn( "Context" );
|
||||
rp.AddStringColumn( "System" );
|
||||
rp.AddIntColumn( "Valid", CGCReportPrinter::eSummary_Total );
|
||||
rp.AddIntColumn( "Invalid", CGCReportPrinter::eSummary_Total );
|
||||
rp.AddIntColumn( "InvalidID", CGCReportPrinter::eSummary_Total );
|
||||
|
||||
FOR_EACH_MAP_FAST( m_Systems, nSystem )
|
||||
{
|
||||
const CGCAccessSystem* pSystem = m_Systems[ nSystem ];
|
||||
if( pszSystemFilter && V_stricmp( pszSystemFilter, pSystem->m_sName ) )
|
||||
continue;
|
||||
|
||||
FOR_EACH_MAP_FAST( pSystem->m_Jobs, nJob )
|
||||
{
|
||||
const CGCAccessSystem::AccessStats_t& stats = pSystem->m_Jobs[ nJob ].m_Stats;
|
||||
const char* pszJob = ( const char* )pSystem->m_Jobs.Key( nJob );
|
||||
const char* pszContext = ( const char* )pSystem->m_Jobs[ nJob ].m_sContext;
|
||||
|
||||
if( !ShouldDisplayStats( eDisplay, stats ) )
|
||||
continue;
|
||||
if( pszJobFilter && V_stricmp( pszJobFilter, pszJob ) )
|
||||
continue;
|
||||
if( pszContextFilter && V_stricmp( pszContextFilter, pszContext ) )
|
||||
continue;
|
||||
|
||||
rp.StrValue( pszJob, CFmtStr( "gcaccess_dump_job \"%s\" %d", pszJob, eDisplay ).String() );
|
||||
rp.StrValue( pszContext, CFmtStr( "gcaccess_dump_context \"%s\" %d", pszContext, eDisplay ).String() );
|
||||
rp.StrValue( pSystem->m_sName, CFmtStr( "gcaccess_dump_system \"%s\" %d", pSystem->m_sName.String(), eDisplay ).String() );
|
||||
rp.IntValue( stats.m_nNumValid );
|
||||
rp.IntValue( stats.m_nNumFail );
|
||||
rp.IntValue( stats.m_nNumFailID );
|
||||
rp.CommitRow();
|
||||
}
|
||||
}
|
||||
|
||||
rp.SortReport( "Context", false );
|
||||
rp.PrintReport( SPEW_CONSOLE );
|
||||
}
|
||||
|
||||
void CGCAccess::DependencyReport( const char* pszSystem, EDisplay eDisplay ) const
|
||||
{
|
||||
//first off, we need to find the system we are scanning for dependencies on
|
||||
const CGCAccessSystem* pMatchSystem = NULL;
|
||||
FOR_EACH_MAP_FAST( m_Systems, nSystem )
|
||||
{
|
||||
const CGCAccessSystem* pSystem = m_Systems[ nSystem ];
|
||||
if( V_stricmp( pszSystem, pSystem->m_sName ) == 0 )
|
||||
{
|
||||
pMatchSystem = pSystem;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( !pMatchSystem )
|
||||
{
|
||||
EG_MSG( SPEW_CONSOLE, "Unable to find system %s for dependency report\n", pszSystem );
|
||||
return;
|
||||
}
|
||||
|
||||
//now generate our report
|
||||
CGCReportPrinter rp;
|
||||
rp.AddStringColumn( "Job" );
|
||||
rp.AddStringColumn( "Context" );
|
||||
rp.AddStringColumn( "System" );
|
||||
rp.AddIntColumn( "Valid", CGCReportPrinter::eSummary_Total );
|
||||
rp.AddIntColumn( "Invalid", CGCReportPrinter::eSummary_Total );
|
||||
rp.AddIntColumn( "InvalidID", CGCReportPrinter::eSummary_Total );
|
||||
|
||||
FOR_EACH_MAP_FAST( m_Systems, nSystem )
|
||||
{
|
||||
const CGCAccessSystem* pSystem = m_Systems[ nSystem ];
|
||||
//skip ourself
|
||||
if( pSystem == pMatchSystem )
|
||||
continue;
|
||||
|
||||
FOR_EACH_MAP_FAST( pSystem->m_Jobs, nJob )
|
||||
{
|
||||
const CGCAccessSystem::AccessStats_t& stats = pSystem->m_Jobs[ nJob ].m_Stats;
|
||||
const char* pszJob = ( const char* )pSystem->m_Jobs.Key( nJob );
|
||||
const char* pszContext = ( const char* )pSystem->m_Jobs[ nJob ].m_sContext;
|
||||
|
||||
//skip any job that isn't using our match system
|
||||
if( !pMatchSystem->m_Jobs.HasElement( ( uintp )pszJob ) )
|
||||
continue;
|
||||
if( !ShouldDisplayStats( eDisplay, stats ) )
|
||||
continue;
|
||||
|
||||
rp.StrValue( pszJob, CFmtStr( "gcaccess_dump_job \"%s\" %d", pszJob, eDisplay ).String() );
|
||||
rp.StrValue( pszContext, CFmtStr( "gcaccess_dump_context \"%s\" %d", pszContext, eDisplay ).String() );
|
||||
rp.StrValue( pSystem->m_sName, CFmtStr( "gcaccess_dump_system \"%s\" %d", pSystem->m_sName.String(), eDisplay ).String() );
|
||||
rp.IntValue( stats.m_nNumValid );
|
||||
rp.IntValue( stats.m_nNumFail );
|
||||
rp.IntValue( stats.m_nNumFailID );
|
||||
rp.CommitRow();
|
||||
}
|
||||
}
|
||||
|
||||
rp.SortReport( "System", false );
|
||||
rp.PrintReport( SPEW_CONSOLE );
|
||||
|
||||
|
||||
}
|
||||
|
||||
GC_CON_COMMAND_PARAMS( gcaccess_dump_job, 1, "<Job Name> Dumps all of the accesses associated with the specified job" )
|
||||
{
|
||||
CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced;
|
||||
GGCAccess().FullReport( NULL, NULL, args[ 1 ], eDisplay );
|
||||
}
|
||||
|
||||
GC_CON_COMMAND_PARAMS( gcaccess_dump_context, 1, "<Job Name> Dumps all of the accesses associated with the specified context" )
|
||||
{
|
||||
CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced;
|
||||
GGCAccess().FullReport( NULL, args[ 1 ], NULL, eDisplay );
|
||||
}
|
||||
|
||||
GC_CON_COMMAND_PARAMS( gcaccess_dump_system, 1, "<Job Name> Dumps all of the accesses associated with the specified system" )
|
||||
{
|
||||
CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced;
|
||||
GGCAccess().FullReport( args[ 1 ], NULL, NULL, eDisplay );
|
||||
}
|
||||
|
||||
GC_CON_COMMAND_PARAMS( gcaccess_dump_context_jobs, 1, "<Context Name> Dumps all of the accesses associated with the specified context" )
|
||||
{
|
||||
CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced;
|
||||
GGCAccess().ReportJobs( args[ 1 ], eDisplay );
|
||||
}
|
||||
|
||||
GC_CON_COMMAND_PARAMS( gcaccess_dump_context_systems, 1, "<Context Name> Dumps all of the accesses associated with the specified context" )
|
||||
{
|
||||
CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced;
|
||||
GGCAccess().ReportSystems( args[ 1 ], eDisplay );
|
||||
}
|
||||
|
||||
GC_CON_COMMAND_PARAMS( gcaccess_dump_system_dependencies, 1, "<System Name> This will dump out for all jobs that reference the specified system, what other systems they reference" )
|
||||
{
|
||||
CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced;
|
||||
GGCAccess().DependencyReport( args[ 1 ], eDisplay );
|
||||
}
|
||||
|
||||
GC_CON_COMMAND( gcaccess_dump_jobs, "Dumps all the jobs that have been tracked with the gcaccess system as well as count of violations" )
|
||||
{
|
||||
CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 2 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 1 ] ) : CGCAccess::eDisplay_Referenced;
|
||||
GGCAccess().ReportJobs( NULL, eDisplay);
|
||||
}
|
||||
|
||||
GC_CON_COMMAND( gcaccess_dump_systems, "Dumps all the systems that have been tracked with the gcaccess system as well as count of violations" )
|
||||
{
|
||||
CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 2 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 1 ] ) : CGCAccess::eDisplay_Referenced;
|
||||
GGCAccess().ReportSystems( NULL, eDisplay );
|
||||
}
|
||||
|
||||
GC_CON_COMMAND( gcaccess_dump_full, "Dumps all the information tracked by the gcaccess" )
|
||||
{
|
||||
CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 2 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 1 ] ) : CGCAccess::eDisplay_Referenced;
|
||||
GGCAccess().FullReport( NULL, NULL, NULL, eDisplay );
|
||||
}
|
||||
|
||||
GC_CON_COMMAND_PARAMS( gcaccess_catch_context, 2, "<system> <context> - Catches and generates an assert when the specified context calls into the provided system. This will only generate a single assert" )
|
||||
{
|
||||
if( !GGCAccess().CatchSingleAssert( args[ 1 ], true, args[ 2 ] ) )
|
||||
EG_MSG( SPEW_CONSOLE, "Unable to register catch, likely \'%s\' is not a valid system.", args[ 1 ] );
|
||||
}
|
||||
|
||||
GC_CON_COMMAND_PARAMS( gcaccess_catch_job, 2, "<system> <job> - Catches and generates an assert when the specified job calls into the provided system. This will only generate a single assert" )
|
||||
{
|
||||
if( !GGCAccess().CatchSingleAssert( args[ 1 ], false, args[ 2 ] ) )
|
||||
EG_MSG( SPEW_CONSOLE, "Unable to register catch, likely \'%s\' is not a valid system.", args[ 1 ] );
|
||||
}
|
||||
|
||||
GC_CON_COMMAND( gcaccess_catch_clear, "Clears all previously specified catches" )
|
||||
{
|
||||
GGCAccess().ClearSingleAsserts();
|
||||
}
|
||||
|
||||
} //namespace GCSDK
|
||||
@@ -0,0 +1,203 @@
|
||||
//====== Copyright 1996-2010, Valve Corporation, All rights reserved. =======
|
||||
//
|
||||
// Purpose: The file defines our Google Protocol Buffers which are used in over
|
||||
// the wire messages between servers as well as between clients and servers.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
// We care more about speed than code size
|
||||
option optimize_for = SPEED;
|
||||
|
||||
// We don't use the service generation functionality
|
||||
option cc_generic_services = false;
|
||||
|
||||
|
||||
//
|
||||
// STYLE NOTES:
|
||||
//
|
||||
// Use CamelCase CMsgMyMessageName style names for messages.
|
||||
//
|
||||
// Use lowercase _ delimited names like my_steam_id for field names, this is non-standard for Steam,
|
||||
// but plays nice with the Google formatted code generation.
|
||||
//
|
||||
// Try not to use required fields ever. Only do so if you are really really sure you'll never want them removed.
|
||||
// Optional should be preffered as it will make versioning easier and cleaner in the future if someone refactors
|
||||
// your message and wants to remove or rename fields.
|
||||
//
|
||||
// Use fixed64 for JobId_t, GID_t, or SteamID. This is appropriate for any field that is normally
|
||||
// going to be larger than 2^56. Otherwise use int64 for 64 bit values that are frequently smaller
|
||||
// than 2^56 as it will safe space on the wire in those cases.
|
||||
//
|
||||
// Similar to fixed64, use fixed32 for RTime32 or other 32 bit values that are frequently larger than
|
||||
// 2^28. It will safe space in those cases, otherwise use int32 which will safe space for smaller values.
|
||||
// An exception to this rule for RTime32 is if the value will frequently be zero rather than set to an actual
|
||||
// time.
|
||||
//
|
||||
|
||||
enum EGCSystemMsg
|
||||
{
|
||||
k_EGCMsgInvalid = 0;
|
||||
k_EGCMsgMulti = 1;
|
||||
|
||||
k_EGCMsgGenericReply = 10;
|
||||
|
||||
k_EGCMsgSystemBase = 50;
|
||||
k_EGCMsgAchievementAwarded = 51;
|
||||
k_EGCMsgConCommand = 52; // A command from the GC's admin console
|
||||
k_EGCMsgStartPlaying = 53;
|
||||
k_EGCMsgStopPlaying = 54;
|
||||
k_EGCMsgStartGameserver = 55;
|
||||
k_EGCMsgStopGameserver = 56;
|
||||
k_EGCMsgWGRequest = 57;
|
||||
k_EGCMsgWGResponse = 58;
|
||||
k_EGCMsgGetUserGameStatsSchema = 59; // Gets the user game stats schema for the app
|
||||
k_EGCMsgGetUserGameStatsSchemaResponse = 60;
|
||||
k_EGCMsgGetUserStatsDEPRECATED = 61; // Gets user game stats for a user
|
||||
k_EGCMsgGetUserStatsResponse = 62;
|
||||
k_EGCMsgAppInfoUpdated = 63; // Message sent to the GC when there has been an AppInfo update
|
||||
k_EGCMsgValidateSession = 64; // Message sent by the GC when it wants to make sure a session exists
|
||||
k_EGCMsgValidateSessionResponse = 65; // Message sent to the GC in response to ValidateSession
|
||||
k_EGCMsgLookupAccountFromInput = 66; // Sent by the GC to lookup user. Reply is k_EGCMsgGenericReply
|
||||
k_EGCMsgSendHTTPRequest = 67; // Message sent by the GC to do a generic HTTP request
|
||||
k_EGCMsgSendHTTPRequestResponse = 68; // Response back to the GC with the results of the HTTP request
|
||||
k_EGCMsgPreTestSetup = 69; // Reset the GC database (usually for testing purposes)
|
||||
k_EGCMsgRecordSupportAction = 70; // Logs a support action
|
||||
k_EGCMsgGetAccountDetails_DEPRECATED = 71; // DEPRECATED - Use protobuf-based k_EGCMsgGetAccountDetails //Requests the details for an account
|
||||
// k_EGCMsgSendInterAppMessage = 72; // Sends a message to another app's GC
|
||||
k_EGCMsgReceiveInterAppMessage = 73; // Receives a message from another app's GC
|
||||
k_EGCMsgFindAccounts = 74; // queries the AMs for accounts by name
|
||||
k_EGCMsgPostAlert = 75; // posts an alert to Steam
|
||||
k_EGCMsgGetLicenses = 76; // asks Steam for the user's licenses
|
||||
k_EGCMsgGetUserStats = 77; // Gets user game stats for a user
|
||||
k_EGCMsgGetCommands = 78; // request for a list of commands from a gc console
|
||||
k_EGCMsgGetCommandsResponse = 79; // response with a list of commands for a gc console
|
||||
k_EGCMsgAddFreeLicense = 80; // request for for Steam to add a license to the specified free package
|
||||
k_EGCMsgAddFreeLicenseResponse = 81; // response with the result of the attempt to add a free license
|
||||
k_EGCMsgGetIPLocation = 82; // Get geolocation data for a specific IP
|
||||
k_EGCMsgGetIPLocationResponse = 83; // Geolocation response
|
||||
|
||||
k_EGCMsgSystemStatsSchema = 84; // Message sent by the GC specifying what its stats schema is
|
||||
k_EGCMsgGetSystemStats = 85; // Message sent to the GC requesting its stats
|
||||
k_EGCMsgGetSystemStatsResponse = 86; // Message sent by the GC with its stats
|
||||
|
||||
k_EGCMsgSendEmail = 87; // Sent by the GC to send an email to a user
|
||||
k_EGCMsgSendEmailResponse = 88; // Response with the result of the send request
|
||||
k_EGCMsgGetEmailTemplate = 89; // Sent to the GC to request an email template
|
||||
k_EGCMsgGetEmailTemplateResponse = 90; // Get email template response
|
||||
k_EGCMsgGrantGuestPass = 91; // Sends a guest pass/gift to a user
|
||||
k_EGCMsgGrantGuestPassResponse = 92; // Send guest pass response
|
||||
k_EGCMsgGetAccountDetails = 93; // Requests the details for an account
|
||||
k_EGCMsgGetAccountDetailsResponse = 94; // Requests the details for an account response
|
||||
k_EGCMsgGetPersonaNames = 95; // Gets the persona names for a set of accounts
|
||||
k_EGCMsgGetPersonaNamesResponse = 96; // Gets the persona names for a set of accounts response
|
||||
k_EGCMsgMultiplexMsg = 97; // Sends a single message to multiple users
|
||||
|
||||
// web API calls
|
||||
k_EGCMsgWebAPIRegisterInterfaces = 101; // sent once at startup to register APIs
|
||||
k_EGCMsgWebAPIJobRequest = 102; // sent when an actual request is made
|
||||
// k_EGCMsgWebAPIRegistrationRequested = 103; // sent by the GC Host when it learns a web API server has started -- No longer sent by the GCH
|
||||
k_EGCMsgWebAPIJobRequestHttpResponse = 104; // sent by the GC in response to k_EGCMsgWebAPIJobRequest - uses CMsgHttpResponse
|
||||
k_EGCMsgWebAPIJobRequestForwardResponse = 105; // sent by the GC in response to k_EGCMsgWebAPIJobRequest - uses CMsgGCMsgWebAPIJobRequestForwardResponse
|
||||
|
||||
|
||||
// Memcached
|
||||
k_EGCMsgMemCachedGet = 200; // Get key(s) from memcached
|
||||
k_EGCMsgMemCachedGetResponse = 201; // Retrieved keys
|
||||
k_EGCMsgMemCachedSet = 202; // Set key(s) into memcached
|
||||
k_EGCMsgMemCachedDelete = 203; // Delete key(s) from memcached
|
||||
k_EGCMsgMemCachedStats = 204; // Request stats from memcached
|
||||
k_EGCMsgMemCachedStatsResponse = 205; // Retrieved stats
|
||||
|
||||
// SQL
|
||||
k_EGCMsgSQLStats = 210; // Get the current SQL stats
|
||||
k_EGCMsgSQLStatsResponse = 211; // Retrieved stats
|
||||
|
||||
// Multi-GC setup
|
||||
k_EGCMsgMasterSetDirectory = 220; // The master GC will send this back to the GCH indicating the sub GC's that need to be created
|
||||
k_EGCMsgMasterSetDirectoryResponse = 221; // Response back from the GCH to the master GC regarding the successful receiving of the directory (note this doesn't mean all sub GCs are done starting up)
|
||||
k_EGCMsgMasterSetWebAPIRouting = 222; // The master GC will send this back to the GCH indicating the routing details for WebAPI requests
|
||||
k_EGCMsgMasterSetWebAPIRoutingResponse = 223; // Response back from the GCH to the master GC regarding the successful receipt and processing of k_EGCMsgMasterSetWebAPIRouting
|
||||
k_EGCMsgMasterSetClientMsgRouting = 224; // The master GC will send this back to the GCH indicating the routing details for client messages
|
||||
k_EGCMsgMasterSetClientMsgRoutingResponse = 225; // Response back from the GCH to the master GC regarding the successful receipt and processing of k_EGCMsgMasterSetClientMsgRouting
|
||||
k_EGCMsgSetOptions = 226; // Each GC can send this back to the GCH indicating which client messages and data streams the GC is interested in
|
||||
k_EGCMsgSetOptionsResponse = 227; // Response back from the GCH (optional - don't send a job ID with SetOptions to skip the response)
|
||||
|
||||
// Newer GC SDK messages. All should be protobuf based.
|
||||
k_EGCMsgSystemBase2 = 500;
|
||||
k_EGCMsgGetPurchaseTrustStatus = 501; // Gets information about a user's purchase trustworthiness
|
||||
k_EGCMsgGetPurchaseTrustStatusResponse = 502; // Gets information about a user's purchase trustworthiness response
|
||||
k_EGCMsgUpdateSession = 503; // GCH update regarding the status of a session, only sent if the convar to enable these messages is set
|
||||
k_EGCMsgGCAccountVacStatusChange = 504; // Accounts VAC status has changed
|
||||
k_EGCMsgCheckFriendship = 505; // GC checking if two users are friends
|
||||
k_EGCMsgCheckFriendshipResponse = 506; // Response to the GC regarding the users' friendship
|
||||
k_EGCMsgGetPartnerAccountLink = 507; // Request the partner account link details for a user
|
||||
k_EGCMsgGetPartnerAccountLinkResponse = 508; // Response to the GC with the partner account link details
|
||||
k_EGCMsgVSReportedSuspiciousActivity = 509; // Suspicious activity detected on user's account
|
||||
k_EGCMsgAccountTradeBanStatusChange = 510; // Account's trade ban status has changed
|
||||
k_EGCMsgAccountLockStatusChange = 511; // Account's lock status has changed
|
||||
k_EGCMsgDPPartnerMicroTxns = 512; // A set of microtransaction data relayed to the DP for insert or update.
|
||||
k_EGCMsgDPPartnerMicroTxnsResponse = 513; // ... Response
|
||||
k_EGCMsgGetIPASN = 514; // Get ASN data for a specific IP
|
||||
k_EGCMsgGetIPASNResponse = 515; // IP ASN response
|
||||
k_EGCMsgGetAppFriendsList = 516; // GC requesting a list of app-friends for a user (defined as friends who play the app requesting the friends list)
|
||||
k_EGCMsgGetAppFriendsListResponse = 517; // ... Response
|
||||
k_EGCMsgVacVerificationChange = 518; // From VS to GC
|
||||
k_EGCMsgAccountPhoneNumberChange = 519; // From Phone Server to GC
|
||||
k_EGCMsgAccountTwoFactorChange = 520; // From AM to GC
|
||||
k_EGCMsgCheckClanMembership = 521; // Checks if an account is a member of a given clan
|
||||
k_EGCMsgCheckClanMembershipResponse = 522; // Checks if an account is a member of a given clan response
|
||||
};
|
||||
|
||||
enum ESOMsg
|
||||
{
|
||||
k_ESOMsg_Create = 21;
|
||||
k_ESOMsg_Update = 22;
|
||||
k_ESOMsg_Destroy = 23;
|
||||
k_ESOMsg_CacheSubscribed = 24;
|
||||
k_ESOMsg_CacheUnsubscribed = 25;
|
||||
k_ESOMsg_UpdateMultiple = 26;
|
||||
k_ESOMsg_CacheSubscriptionCheck = 27; // need to get rid of this!
|
||||
k_ESOMsg_CacheSubscriptionRefresh = 28;
|
||||
k_ESOMsg_CacheSubscribedUpToDate = 29;
|
||||
};
|
||||
|
||||
enum EGCBaseClientMsg
|
||||
{
|
||||
k_EMsgGCPingRequest = 3001; // GC <=> client
|
||||
k_EMsgGCPingResponse = 3002; // GC <=> client
|
||||
|
||||
// WARNING: These messages were promoted into the gcsdk
|
||||
// from base_gcmessages.proto (which is not part of the gcsdk put is
|
||||
// "shared game code"). But we did not want to change the numbers
|
||||
// for existing messages. Don't add new messages in the 4000 range!
|
||||
k_EMsgGCClientWelcome = 4004; // GC => client
|
||||
k_EMsgGCServerWelcome = 4005; // GC => server
|
||||
k_EMsgGCClientHello = 4006; // client => GC
|
||||
k_EMsgGCServerHello = 4007; // server => GC
|
||||
|
||||
k_EMsgGCClientGoodbye = 4008; // GC => client
|
||||
k_EMsgGCServerGoodbye = 4009; // GC => server
|
||||
|
||||
// !FIXME! DOTAMERGE
|
||||
//k_EMsgGCClientConnectionStatus = 4009; // GC => client
|
||||
//k_EMsgGCServerConnectionStatus = 4010; // GC => server
|
||||
|
||||
};
|
||||
|
||||
enum EGCToGCMsg
|
||||
{
|
||||
k_EGCToGCMsgMasterAck = 150; // Sub GC => Master GC, confirm that we've started up successfully so the master can go when all sub GCs are ready
|
||||
k_EGCToGCMsgMasterAckResponse = 151; // Master GC => Sub GC, the ack came through fine and everything is as expected
|
||||
k_EGCToGCMsgRouted = 152; // Master GC => Sub GC, a routed message that originally came in from a client or the system and has been sent to another GC
|
||||
k_EGCToGCMsgRoutedReply = 153; // Sub GC => Sub/Master GC, a reply message that needs to be sent back from the original GC that received the message
|
||||
k_EMsgGCUpdateSubGCSessionInfo = 154; // Master GC => Other GCs
|
||||
k_EMsgGCRequestSubGCSessionInfo = 155; // Sub GC => Master GC
|
||||
k_EMsgGCRequestSubGCSessionInfoResponse = 156; // Master GC => Sub GC
|
||||
k_EGCToGCMsgMasterStartupComplete = 157; // Master GC => Sub GC - Indicates that the master universe has completed startup, indicating that sub GC's are in a good spot to complete startup
|
||||
k_EMsgGCToGCSOCacheSubscribe = 158; // Master GC => Sub GC - A steam ID has subscribed to another SO cache (or possibly their own)
|
||||
k_EMsgGCToGCSOCacheUnsubscribe = 159; // Master GC => Sub GC - A steam ID has unsubscribed from another SO cache (or possibly their own)
|
||||
}
|
||||
|
||||
|
||||
// Do not remove this comment due to a bug on the Mac OS X protobuf compiler
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: CWAPI code for GC access to the Web API server
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "gcwebapi.h"
|
||||
#include "enumutils.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
CUtlVector< GCWebAPIInterfaceMapCreationFunc_t > & CGCWebAPIInterfaceMapRegistrar::VecInstance()
|
||||
{
|
||||
static CUtlVector< GCWebAPIInterfaceMapCreationFunc_t > sm_vecInterfaceFuncs;
|
||||
return sm_vecInterfaceFuncs;
|
||||
}
|
||||
|
||||
|
||||
ENUMSTRINGS_START( EWebApiParamType )
|
||||
{ k_EWebApiParamTypeInvalid, "Invalid" },
|
||||
{ k_EWebApiParamTypeInt32, "int32" },
|
||||
{ k_EWebApiParamTypeUInt32, "uint32" },
|
||||
{ k_EWebApiParamTypeInt64, "int64" },
|
||||
{ k_EWebApiParamTypeUInt64, "uint64" },
|
||||
{ k_EWebApiParamTypeFloat, "float" },
|
||||
{ k_EWebApiParamTypeString, "string" },
|
||||
{ k_EWebApiParamTypeBool, "bool" },
|
||||
{ k_EWebApiParamTypeRawBinary, "rawbinary" }
|
||||
ENUMSTRINGS_REVERSE( EWebApiParamType, k_EWebApiParamTypeInvalid )
|
||||
@@ -0,0 +1,50 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: implementation of CWebAPIKey
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "gcsdk/msgprotobuf.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Clears key settings
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWebAPIKey::Clear()
|
||||
{
|
||||
m_unAccountID = 0;
|
||||
m_unPublisherGroupID = 0;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Serializes the request into a message object (for proxying between
|
||||
// back-end Steam servers).
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWebAPIKey::SerializeIntoProtoBuf( CMsgWebAPIKey & apiKey ) const
|
||||
{
|
||||
apiKey.set_status( m_eStatus );
|
||||
apiKey.set_account_id( m_unAccountID );
|
||||
apiKey.set_publisher_group_id( m_unPublisherGroupID );
|
||||
apiKey.set_key_id( m_unWebAPIKeyID );
|
||||
apiKey.set_domain( m_sDomain.Get() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Deserializes the response from a message object (for proxying between
|
||||
// back-end Steam servers).
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWebAPIKey::DeserializeFromProtoBuf( const CMsgWebAPIKey & apiKey )
|
||||
{
|
||||
m_eStatus = (EWebAPIKeyStatus)apiKey.status();
|
||||
m_unAccountID = apiKey.account_id();
|
||||
m_unPublisherGroupID = apiKey.publisher_group_id();
|
||||
m_unWebAPIKeyID = apiKey.key_id();
|
||||
m_sDomain = apiKey.domain().c_str();
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
#include "stdafx.h"
|
||||
#include "enumutils.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
ENUMSTRINGS_START( EGCWebApiPrivilege )
|
||||
{ k_EGCWebApiPriv_None, "None" },
|
||||
{ k_EGCWebApiPriv_Account, "Account" },
|
||||
{ k_EGCWebApiPriv_Approved, "Approved" },
|
||||
{ k_EGCWebApiPriv_Session, "Session" },
|
||||
{ k_EGCWebApiPriv_Support, "Support" },
|
||||
{ k_EGCWebApiPriv_Admin, "Admin" },
|
||||
{ k_EGCWebApiPriv_EditApp, "EditApp" },
|
||||
{ k_EGCWebApiPriv_MemberPublisher, "MemberPublisher" },
|
||||
{ k_EGCWebApiPriv_EditPublisher, "EditPublisher" },
|
||||
{ k_EGCWebApiPriv_AccountOptional, "AccountOptional" },
|
||||
ENUMSTRINGS_END( EGCWebApiPrivilege );
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Singleton accessor to registered WG jobs
|
||||
//-----------------------------------------------------------------------------
|
||||
CUtlDict< const JobCreationFunc_t* > &GMapGCWGJobCreationFuncs()
|
||||
{
|
||||
static CUtlDict< const JobCreationFunc_t* > s_MapWGJobCreationFuncs;
|
||||
return s_MapWGJobCreationFuncs;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Singleton accessor to registered WG jobs
|
||||
//-----------------------------------------------------------------------------
|
||||
CUtlDict< const WebApiFunc_t* > &GMapGCWGRequestInfo()
|
||||
{
|
||||
static CUtlDict< const WebApiFunc_t* > s_MapWGRequestInfo;
|
||||
return s_MapWGRequestInfo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CGCWGJobMgr::CGCWGJobMgr( )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CGCWGJobMgr::~CGCWGJobMgr()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Hub has receieved a k_EMsgWGRequest message, find & dispatch WG Job
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CGCWGJobMgr::BHandleMsg( IMsgNetPacket *pNetPacket )
|
||||
{
|
||||
CGCMsg<MsgGCWGRequest_t> msg( pNetPacket );
|
||||
AssertMsg( msg.GetEMsg() == k_EGCMsgWGRequest, "GCWGJobMgr asked to route a message, but it is not a MsgWGRequest" );
|
||||
|
||||
CUtlString strRequestName;
|
||||
msg.BReadStr( &strRequestName );
|
||||
|
||||
int iJobType = GMapGCWGRequestInfo().Find( strRequestName.Get() );
|
||||
|
||||
if ( !GMapGCWGRequestInfo().IsValidIndex( iJobType ) )
|
||||
{
|
||||
EmitError( SPEW_GC, "Unable to find GC WG handler %s", strRequestName.Get() );
|
||||
SendErrorMessage( msg, "Unknown method", k_EResultInvalidParam );
|
||||
return false;
|
||||
}
|
||||
|
||||
const WebApiFunc_t * pFunc = GMapGCWGRequestInfo()[iJobType];
|
||||
if( !BVerifyPrivileges( msg, pFunc ) )
|
||||
return false;
|
||||
|
||||
CGCWGJob *job = (CGCWGJob *)(*((GMapGCWGJobCreationFuncs())[iJobType]))( GGCBase(), NULL );
|
||||
Assert( job );
|
||||
|
||||
job->SetWebApiFunc( pFunc );
|
||||
|
||||
job->StartJobFromNetworkMsg( pNetPacket, msg.Hdr().m_JobIDSource );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool CGCWGJobMgr::BVerifyPrivileges( const CGCMsg<MsgGCWGRequest_t> & msg, const WebApiFunc_t * pFunc )
|
||||
{
|
||||
if(msg.Body().m_unPrivilege != (uint32)pFunc->m_eRequiredPrivilege )
|
||||
{
|
||||
SendErrorMessage( msg,
|
||||
CFmtStr( "Privilege mismatch in %s gc call. Expected: %s, actual: %s", pFunc->m_pchRequestName, PchNameFromEGCWebApiPrivilege( pFunc->m_eRequiredPrivilege ), PchNameFromEGCWebApiPrivilege( (EGCWebApiPrivilege)msg.Body().m_unPrivilege ) ),
|
||||
k_EResultAccessDenied );
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Sends an error message in response to a WG request
|
||||
// Inputs: msg - The message we're responding to. This causes the error to
|
||||
// be routed to the right place and eventually back to the right
|
||||
// browser.
|
||||
// pchErrorMsg - The message to display
|
||||
// nResult - The error code to return
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGCWGJobMgr::SendErrorMessage( const CGCMsg<MsgGCWGRequest_t> & msg, const char *pchErrorMsg, int32 nResult )
|
||||
{
|
||||
KeyValuesAD pkvErr( "error" );
|
||||
pkvErr->SetString( "error", pchErrorMsg );
|
||||
pkvErr->SetInt( "success", nResult );
|
||||
|
||||
SendResponse( msg, pkvErr, false );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Sets an error message in a response to a WG request
|
||||
// Inputs: pkvResponse - The response KV block to set the error in
|
||||
// pchErrorMsg - The message to display
|
||||
// nResult - The error code to return
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGCWGJobMgr::SetErrorMessage( KeyValues *pkvErr, const char *pchErrorMsg, int32 nResult )
|
||||
{
|
||||
pkvErr->SetString( "error", pchErrorMsg );
|
||||
pkvErr->SetInt( "success", nResult );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Sends a message in response to a WG request
|
||||
// Inputs: msg - The message we're responding to. This causes the error to
|
||||
// be routed to the right place and eventually back to the right
|
||||
// browser.
|
||||
// pkvResponse - The KeyValues containing the response data
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGCWGJobMgr::SendResponse( const CGCMsg<MsgGCWGRequest_t> & msg, KeyValues *pkvResponse, bool bResult )
|
||||
{
|
||||
//prepare response msg
|
||||
CGCMsg<MsgGCWGResponse_t> msgResponse( k_EGCMsgWGResponse, msg );
|
||||
CUtlBuffer bufResponse;
|
||||
KVPacker packer;
|
||||
if ( packer.WriteAsBinary( pkvResponse, bufResponse ) )
|
||||
{
|
||||
msgResponse.AddVariableLenData( bufResponse.Base(), bufResponse.TellPut() );
|
||||
msgResponse.Body().m_cubKeyValues = bufResponse.TellPut();
|
||||
msgResponse.Body().m_bResult = bResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
msgResponse.Body().m_cubKeyValues = 0;
|
||||
AssertMsg( false, "Failed to serialize WG response" );
|
||||
}
|
||||
msgResponse.Hdr().m_JobIDTarget = msg.Hdr().m_JobIDSource;
|
||||
|
||||
GGCBase()->BSendSystemMessage( msgResponse );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Adds a WG job handler to the global list
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGCWGJobMgr::RegisterWGJob( const WebApiFunc_t *pWGJobType, const JobType_t *pJobCreationFunc )
|
||||
{
|
||||
const char *pchRequestName = pWGJobType->m_pchRequestName;
|
||||
GMapGCWGJobCreationFuncs().Insert( pchRequestName, &(pJobCreationFunc->m_pJobFactory) );
|
||||
GMapGCWGRequestInfo().Insert( pchRequestName, pWGJobType );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the list of registered WG jobs
|
||||
//-----------------------------------------------------------------------------
|
||||
CUtlDict< const WebApiFunc_t* > &CGCWGJobMgr::GetWGRequestMap()
|
||||
{
|
||||
return GMapGCWGRequestInfo();
|
||||
}
|
||||
|
||||
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
void CGCWGJobMgr::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
}
|
||||
|
||||
void CGCWGJobMgr::ValidateStatics( CValidator &validator )
|
||||
{
|
||||
ValidateObj( GMapGCWGJobCreationFuncs() );
|
||||
ValidateObj( GMapGCWGRequestInfo() );
|
||||
}
|
||||
|
||||
#endif // DBGFLAG_VALIDATE
|
||||
|
||||
|
||||
|
||||
} // namespace GCSDK
|
||||
+710
@@ -0,0 +1,710 @@
|
||||
//====== Copyright © 1996-2010, Valve Corporation, All rights reserved. =======
|
||||
//
|
||||
// Purpose: HTTP related enums and objects, stuff that both clients and server use should go here
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include "stdafx.h"
|
||||
#include <time.h>
|
||||
|
||||
#include "msgprotobuf.h"
|
||||
#include "rtime.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
using namespace GCSDK;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Check if the given status code is one that by spec allows a body, or is
|
||||
// one that "MUST NOT" include an entity body
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CHTTPUtil::BStatusCodeAllowsBody( EHTTPStatusCode eHTTPStatus )
|
||||
{
|
||||
switch( eHTTPStatus )
|
||||
{
|
||||
case k_EHTTPStatusCode100Continue:
|
||||
case k_EHTTPStatusCode101SwitchingProtocols:
|
||||
case k_EHTTPStatusCode204NoContent:
|
||||
case k_EHTTPStatusCode205ResetContent:
|
||||
case k_EHTTPStatusCode304NotModified:
|
||||
return false;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Wrapper to ease use of Steam Web APIs
|
||||
//-----------------------------------------------------------------------------
|
||||
CSteamAPIRequest::CSteamAPIRequest( EHTTPMethod eMethod, const char *pchInterface, const char *pchMethod, int nVersion ) :
|
||||
CHTTPRequest( eMethod, "api.steampowered.com", CFmtStr( "/%s/%s/v%04d/", pchInterface, pchMethod, nVersion ) )
|
||||
{
|
||||
if ( k_EHTTPMethodPOST == eMethod )
|
||||
{
|
||||
SetPOSTParamString( "format", "vdf" );
|
||||
SetPOSTParamString( "key", GGCBase()->GetSteamAPIKey() );
|
||||
}
|
||||
else
|
||||
{
|
||||
SetGETParamString( "format", "vdf" );
|
||||
SetGETParamString( "key", GGCBase()->GetSteamAPIKey() );
|
||||
}
|
||||
|
||||
EUniverse universe = GGCHost()->GetUniverse();
|
||||
if ( universe == k_EUniverseBeta )
|
||||
{
|
||||
SetHostname( "api-beta.steampowered.com" );
|
||||
}
|
||||
else if ( universe == k_EUniverseDev )
|
||||
{
|
||||
// Set this to your dev universe API endpoint if not local
|
||||
SetHostname( "localhost:8282" );
|
||||
}
|
||||
}
|
||||
|
||||
CHTTPRequest::CHTTPRequest( CMsgHttpRequest* pProto )
|
||||
{
|
||||
Init( pProto );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Construct a request object, it's invalid until data is setup
|
||||
//-----------------------------------------------------------------------------
|
||||
CHTTPRequest::CHTTPRequest()
|
||||
{
|
||||
Init( NULL );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Common init code
|
||||
//-----------------------------------------------------------------------------
|
||||
void CHTTPRequest::Init( CMsgHttpRequest* pProto )
|
||||
{
|
||||
//see if we are wrapping a proto that already exists
|
||||
if( pProto )
|
||||
{
|
||||
m_pProto = pProto;
|
||||
m_bOwnProto = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pProto = new CMsgHttpRequest;
|
||||
m_bOwnProto = true;
|
||||
|
||||
SetEHTTPMethod( k_EHTTPMethodInvalid );
|
||||
// Default URL
|
||||
SetURL( "/" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CHTTPRequest::CHTTPRequest( EHTTPMethod eMethod, const char *pchHost, const char *pchRelativeURL )
|
||||
{
|
||||
Init( NULL );
|
||||
|
||||
SetEHTTPMethod( eMethod );
|
||||
SetHostname( pchHost );
|
||||
SetURL( pchRelativeURL );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CHTTPRequest::CHTTPRequest( EHTTPMethod eMethod, const char *pchAbsoluteURL )
|
||||
{
|
||||
Init( NULL );
|
||||
|
||||
SetEHTTPMethod( eMethod );
|
||||
|
||||
// We need to break the URL down into host + relativeURL
|
||||
const char *pchHost = Q_strstr( pchAbsoluteURL, "://" );
|
||||
if ( pchHost )
|
||||
{
|
||||
// Skip past protocol
|
||||
pchHost += 3;
|
||||
|
||||
// Find the next /, that is the beginning of the actual URL
|
||||
const char *pchURL = Q_strstr( pchHost, "/");
|
||||
if ( !pchURL )
|
||||
{
|
||||
// No URL specified after host? just default to / then.
|
||||
SetURL( "/" );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ok, now we must remove the query string
|
||||
const char *pchQueryString = Q_strstr( pchURL, "?" );
|
||||
if ( !pchQueryString )
|
||||
{
|
||||
// No query string, full thing is the URL
|
||||
SetURL( pchURL );
|
||||
}
|
||||
else
|
||||
{
|
||||
// URL is everything before query string
|
||||
SetURLDirect( pchURL, pchQueryString - pchURL );
|
||||
|
||||
Assert( *pchQueryString == '?' );
|
||||
pchQueryString++;
|
||||
if( *pchQueryString )
|
||||
{
|
||||
m_pProto->mutable_get_params()->Clear();
|
||||
CUtlVector<CMsgHttpRequest_QueryParam> vecParams;
|
||||
RetrieveURLEncodedData( pchQueryString, Q_strlen( pchQueryString ), vecParams );
|
||||
FOR_EACH_VEC( vecParams, i )
|
||||
{
|
||||
*m_pProto->add_get_params() = vecParams[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Is there a userinfo separator in the hostname portion? We don't support
|
||||
// username/password authentication in the URL, but we must still skip it.
|
||||
const char *pchUserinfoSep = strchr( pchHost, '@' );
|
||||
if ( pchUserinfoSep && ( !pchURL || pchUserinfoSep < pchURL ) )
|
||||
{
|
||||
pchHost = pchUserinfoSep + 1;
|
||||
}
|
||||
|
||||
// If we found a URL only set upto that, otherwise set everything as host
|
||||
if ( pchURL )
|
||||
SetHostnameDirect( pchHost, pchURL-pchHost );
|
||||
else
|
||||
SetHostname( pchHost );
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertMsg( false, "Bad absolute URL to CHTTPRequest constructor, must start with protocol://" );
|
||||
EmitError( SPEW_GC, "Bad absolute URL to CHTTPRequest constructor, must start with protocol://" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CHTTPRequest::~CHTTPRequest()
|
||||
{
|
||||
if( m_bOwnProto )
|
||||
delete m_pProto;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the value of a GET parameter, this is case-insensitive by default.
|
||||
//-----------------------------------------------------------------------------
|
||||
const CMsgHttpRequest_QueryParam *CHTTPRequest::GetGETParam( const char *pchGetParamName, bool bMatchCase ) const
|
||||
{
|
||||
const uint32 nNumParams = m_pProto->get_params_size();
|
||||
for( uint32 nParam = 0; nParam < nNumParams; nParam++ )
|
||||
{
|
||||
const CMsgHttpRequest_QueryParam& param = m_pProto->get_params( nParam );
|
||||
if ( bMatchCase )
|
||||
{
|
||||
if ( Q_strcmp( param.name().c_str(), pchGetParamName ) == 0 )
|
||||
return ¶m;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( Q_stricmp( param.name().c_str(), pchGetParamName ) == 0 )
|
||||
return ¶m;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the value of a GET parameter, this is case-insensitive by default.
|
||||
//-----------------------------------------------------------------------------
|
||||
const char *CHTTPRequest::GetGETParamString( const char *pchGetParamName, const char *pchDefault, bool bMatchCase ) const
|
||||
{
|
||||
const CMsgHttpRequest_QueryParam *pParam = GetGETParam( pchGetParamName, bMatchCase );
|
||||
if ( !pParam )
|
||||
return pchDefault;
|
||||
|
||||
return pParam->value().c_str();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the value of a GET parameter, this is case-insensitive by default.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CHTTPRequest::GetGETParamBool( const char *pchGetParamName, bool bDefault, bool bMatchCase ) const
|
||||
{
|
||||
const CMsgHttpRequest_QueryParam *pParam = GetGETParam( pchGetParamName, bMatchCase );
|
||||
if ( !pParam )
|
||||
return bDefault;
|
||||
|
||||
return ( Q_atoi( pParam->value().c_str() ) != 0);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the value of a GET parameter, this is case-insensitive by default.
|
||||
//-----------------------------------------------------------------------------
|
||||
int32 CHTTPRequest::GetGETParamInt32( const char *pchGetParamName, int32 nDefault, bool bMatchCase ) const
|
||||
{
|
||||
const CMsgHttpRequest_QueryParam *pParam = GetGETParam( pchGetParamName, bMatchCase );
|
||||
if ( !pParam )
|
||||
return nDefault;
|
||||
|
||||
return Q_atoi( pParam->value().c_str() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the value of a GET parameter, this is case-insensitive by default.
|
||||
//-----------------------------------------------------------------------------
|
||||
uint32 CHTTPRequest::GetGETParamUInt32( const char *pchGetParamName, uint32 unDefault, bool bMatchCase ) const
|
||||
{
|
||||
const CMsgHttpRequest_QueryParam *pParam = GetGETParam( pchGetParamName, bMatchCase );
|
||||
if ( !pParam )
|
||||
return unDefault;
|
||||
|
||||
return (uint32)V_atoui64( pParam->value().c_str() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the value of a GET parameter, this is case-insensitive by default.
|
||||
//-----------------------------------------------------------------------------
|
||||
int64 CHTTPRequest::GetGETParamInt64( const char *pchGetParamName, int64 nDefault, bool bMatchCase ) const
|
||||
{
|
||||
const CMsgHttpRequest_QueryParam *pParam = GetGETParam( pchGetParamName, bMatchCase );
|
||||
if ( !pParam )
|
||||
return nDefault;
|
||||
|
||||
return V_atoi64( pParam->value().c_str() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the value of a GET parameter, this is case-insensitive by default.
|
||||
//-----------------------------------------------------------------------------
|
||||
uint64 CHTTPRequest::GetGETParamUInt64( const char *pchGetParamName, uint64 unDefault, bool bMatchCase ) const
|
||||
{
|
||||
const CMsgHttpRequest_QueryParam *pParam = GetGETParam( pchGetParamName, bMatchCase );
|
||||
if ( !pParam )
|
||||
return unDefault;
|
||||
|
||||
return V_atoui64( pParam->value().c_str() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the value of a GET parameter, this is case-insensitive by default.
|
||||
//-----------------------------------------------------------------------------
|
||||
float CHTTPRequest::GetGETParamFloat( const char *pchGetParamName, float fDefault, bool bMatchCase ) const
|
||||
{
|
||||
const CMsgHttpRequest_QueryParam *pParam = GetGETParam( pchGetParamName, bMatchCase );
|
||||
if ( !pParam )
|
||||
return fDefault;
|
||||
|
||||
return Q_atof( pParam->value().c_str() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the value of a POST parameter, this is case-insensitive by default.
|
||||
//-----------------------------------------------------------------------------
|
||||
const CMsgHttpRequest_QueryParam *CHTTPRequest::GetPOSTParam( const char *pchPostParamName, bool bMatchCase ) const
|
||||
{
|
||||
const uint32 nNumParams = m_pProto->post_params_size();
|
||||
for( uint32 nParam = 0; nParam < nNumParams; nParam++ )
|
||||
{
|
||||
const CMsgHttpRequest_QueryParam& param = m_pProto->post_params( nParam );
|
||||
if ( bMatchCase )
|
||||
{
|
||||
if ( Q_strcmp( param.name().c_str(), pchPostParamName ) == 0 )
|
||||
return ¶m;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( Q_stricmp( param.name().c_str(), pchPostParamName ) == 0 )
|
||||
return ¶m;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the value of a POST parameter, this is case-insensitive by default.
|
||||
//-----------------------------------------------------------------------------
|
||||
const char *CHTTPRequest::GetPOSTParamString( const char *pchGetParamName, const char *pchDefault, bool bMatchCase ) const
|
||||
{
|
||||
const CMsgHttpRequest_QueryParam *pParam = GetPOSTParam( pchGetParamName, bMatchCase );
|
||||
if ( !pParam )
|
||||
return pchDefault;
|
||||
|
||||
return pParam->value().c_str();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the value of a POST parameter, this is case-insensitive by default.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CHTTPRequest::GetPOSTParamBool( const char *pchGetParamName, bool bDefault, bool bMatchCase ) const
|
||||
{
|
||||
const CMsgHttpRequest_QueryParam *pParam = GetPOSTParam( pchGetParamName, bMatchCase );
|
||||
if ( !pParam )
|
||||
return bDefault;
|
||||
|
||||
return ( Q_atoi( pParam->value().c_str() ) != 0);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the value of a POST parameter, this is case-insensitive by default.
|
||||
//-----------------------------------------------------------------------------
|
||||
int32 CHTTPRequest::GetPOSTParamInt32( const char *pchGetParamName, int32 nDefault, bool bMatchCase ) const
|
||||
{
|
||||
const CMsgHttpRequest_QueryParam *pParam = GetPOSTParam( pchGetParamName, bMatchCase );
|
||||
if ( !pParam )
|
||||
return nDefault;
|
||||
|
||||
return Q_atoi( pParam->value().c_str() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the value of a POST parameter, this is case-insensitive by default.
|
||||
//-----------------------------------------------------------------------------
|
||||
uint32 CHTTPRequest::GetPOSTParamUInt32( const char *pchGetParamName, uint32 unDefault, bool bMatchCase ) const
|
||||
{
|
||||
const CMsgHttpRequest_QueryParam *pParam = GetPOSTParam( pchGetParamName, bMatchCase );
|
||||
if ( !pParam )
|
||||
return unDefault;
|
||||
|
||||
return (uint32)V_atoui64( pParam->value().c_str() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the value of a POST parameter, this is case-insensitive by default.
|
||||
//-----------------------------------------------------------------------------
|
||||
int64 CHTTPRequest::GetPOSTParamInt64( const char *pchGetParamName, int64 nDefault, bool bMatchCase ) const
|
||||
{
|
||||
const CMsgHttpRequest_QueryParam *pParam = GetPOSTParam( pchGetParamName, bMatchCase );
|
||||
if ( !pParam )
|
||||
return nDefault;
|
||||
|
||||
return V_atoi64( pParam->value().c_str() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the value of a POST parameter, this is case-insensitive by default.
|
||||
//-----------------------------------------------------------------------------
|
||||
uint64 CHTTPRequest::GetPOSTParamUInt64( const char *pchGetParamName, uint64 unDefault, bool bMatchCase ) const
|
||||
{
|
||||
const CMsgHttpRequest_QueryParam *pParam = GetPOSTParam( pchGetParamName, bMatchCase );
|
||||
if ( !pParam )
|
||||
return unDefault;
|
||||
|
||||
return V_atoui64( pParam->value().c_str() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the value of a POST parameter, this is case-insensitive by default.
|
||||
//-----------------------------------------------------------------------------
|
||||
float CHTTPRequest::GetPOSTParamFloat( const char *pchGetParamName, float fDefault, bool bMatchCase ) const
|
||||
{
|
||||
const CMsgHttpRequest_QueryParam *pParam = GetPOSTParam( pchGetParamName, bMatchCase );
|
||||
if ( !pParam )
|
||||
return fDefault;
|
||||
|
||||
return Q_atof( pParam->value().c_str() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Add a GET param to the request
|
||||
//-----------------------------------------------------------------------------
|
||||
void CHTTPRequest::SetGETParamRaw( const char *pchGetParamName, uint8 *pData, uint32 cubDataLen )
|
||||
{
|
||||
// See if it already exists, and overwrite then (case sensitive!)
|
||||
CMsgHttpRequest_QueryParam *pParam = const_cast< CMsgHttpRequest_QueryParam* >( GetGETParam( pchGetParamName, true ) );
|
||||
if ( !pParam )
|
||||
{
|
||||
// Add a new one then
|
||||
pParam = m_pProto->add_get_params();
|
||||
pParam->set_name( pchGetParamName );
|
||||
}
|
||||
|
||||
pParam->set_value( pData, cubDataLen );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Add a POST param to the request given a string for the name and value
|
||||
//-----------------------------------------------------------------------------
|
||||
void CHTTPRequest::SetPOSTParamRaw( const char *pchPostParamName, uint8 *pData, uint32 cubDataLen )
|
||||
{
|
||||
// See if it already exists, and overwrite then (case sensitive!)
|
||||
CMsgHttpRequest_QueryParam *pParam = const_cast< CMsgHttpRequest_QueryParam* >( GetPOSTParam( pchPostParamName, true ) );
|
||||
if ( !pParam )
|
||||
{
|
||||
// Add a new one then
|
||||
pParam = m_pProto->add_post_params();
|
||||
pParam->set_name( pchPostParamName );
|
||||
}
|
||||
|
||||
pParam->set_value( pData, cubDataLen );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Fetch a request headers value by header name
|
||||
//-----------------------------------------------------------------------------
|
||||
const char * CHTTPRequest::GetRequestHeaderValue( const char *pchRequestHeaderName, const char *pchDefault ) const
|
||||
{
|
||||
const uint32 nNumHeaders = m_pProto->headers_size();
|
||||
for( uint32 nHeader = 0; nHeader < nNumHeaders; nHeader++ )
|
||||
{
|
||||
const CMsgHttpRequest_RequestHeader& header = m_pProto->headers( nHeader );
|
||||
if( Q_stricmp( header.name().c_str(), pchRequestHeaderName ) == 0 )
|
||||
{
|
||||
return header.value().c_str();
|
||||
}
|
||||
}
|
||||
|
||||
return pchDefault;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Set a header field for the request
|
||||
//-----------------------------------------------------------------------------
|
||||
void CHTTPRequest::SetRequestHeaderValue( const char *pchHeaderName, const char *pchHeaderString )
|
||||
{
|
||||
const uint32 nNumHeaders = m_pProto->headers_size();
|
||||
for( uint32 nHeader = 0; nHeader < nNumHeaders; nHeader++ )
|
||||
{
|
||||
CMsgHttpRequest_RequestHeader* pHeader = m_pProto->mutable_headers( nHeader );
|
||||
if( Q_stricmp( pHeader->name().c_str(), pchHeaderName ) == 0 )
|
||||
{
|
||||
pHeader->set_value( pchHeaderString );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CMsgHttpRequest_RequestHeader* pHeader = m_pProto->add_headers();
|
||||
pHeader->set_name( pchHeaderName );
|
||||
pHeader->set_value( pchHeaderString );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Fetch a request header by header name and convert it to a time value
|
||||
// This is just a helpful wrapper of GetRequestHeaderValue that deals with
|
||||
// parsing the time value
|
||||
//-----------------------------------------------------------------------------
|
||||
RTime32 CHTTPRequest::GetRequestHeaderTimeValue( const char *pchRequestHeaderName, RTime32 rtDefault ) const
|
||||
{
|
||||
const char *pchValue = GetRequestHeaderValue( pchRequestHeaderName );
|
||||
if( !pchValue )
|
||||
return rtDefault;
|
||||
|
||||
return CRTime::RTime32FromHTTPDateString( pchValue );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Set the body data for the request
|
||||
//-----------------------------------------------------------------------------
|
||||
void CHTTPRequest::SetBodyData( const void *pubData, uint32 cubData )
|
||||
{
|
||||
m_pProto->set_body( pubData, cubData );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get data out of a query string
|
||||
//-----------------------------------------------------------------------------
|
||||
void CHTTPRequest::RetrieveURLEncodedData( const char *pchQueryString, int nQueryStrLen, CUtlVector<CMsgHttpRequest_QueryParam> &vecParams )
|
||||
{
|
||||
CUtlBuffer bufParamName( 0, 64 );
|
||||
CUtlBuffer bufParamValue( 0, 128 );
|
||||
|
||||
// We shouldn't get passed the ? from a query string, but if we do just skip it and parse ok anyway
|
||||
if ( nQueryStrLen && pchQueryString[0] == '?' )
|
||||
{
|
||||
++pchQueryString;
|
||||
--nQueryStrLen;
|
||||
}
|
||||
|
||||
bool bInParamValue = false;
|
||||
int iTokenStart = 0;
|
||||
for( int i=0; i < nQueryStrLen; ++i )
|
||||
{
|
||||
if ( pchQueryString[i] == '=' && !bInParamValue )
|
||||
{
|
||||
// = switches to value from name, starts a new value token
|
||||
bufParamName.Put( &pchQueryString[iTokenStart], i - iTokenStart );
|
||||
bInParamValue = true;
|
||||
iTokenStart = i + 1;
|
||||
}
|
||||
else if ( pchQueryString[i] == '&' )
|
||||
{
|
||||
// & terminates a value and starts a new name token
|
||||
if ( bInParamValue )
|
||||
{
|
||||
bufParamValue.Put( &pchQueryString[iTokenStart], i - iTokenStart );
|
||||
|
||||
int iIndex = vecParams.AddToTail();
|
||||
CMsgHttpRequest_QueryParam *pParam = &vecParams[iIndex];
|
||||
|
||||
uint32 unNameLen = Q_URLDecode( (char*)bufParamName.Base(), bufParamName.TellPut(), (const char*)bufParamName.Base(), bufParamName.TellPut() );
|
||||
pParam->set_name( (const char*)bufParamName.Base(), unNameLen );
|
||||
|
||||
uint32 unDataLen = (uint32)Q_URLDecode( (char*)bufParamValue.Base(), bufParamValue.TellPut(), (const char*)bufParamValue.Base(), bufParamValue.TellPut() );
|
||||
pParam->set_value( (const uint8*)bufParamValue.Base(), unDataLen );
|
||||
}
|
||||
bufParamName.Clear();
|
||||
bufParamValue.Clear();
|
||||
bInParamValue = false;
|
||||
iTokenStart = i+1;
|
||||
}
|
||||
}
|
||||
|
||||
// Use any left over value from the end of the query string
|
||||
if ( bInParamValue )
|
||||
{
|
||||
bufParamValue.Put( &pchQueryString[iTokenStart], nQueryStrLen - iTokenStart );
|
||||
|
||||
int iIndex = vecParams.AddToTail();
|
||||
CMsgHttpRequest_QueryParam *pParam = &vecParams[iIndex];
|
||||
|
||||
uint32 unNameLen = Q_URLDecode( (char*)bufParamName.Base(), bufParamName.TellPut(), (const char*)bufParamName.Base(), bufParamName.TellPut() );
|
||||
pParam->set_name( (const char*)bufParamName.Base(), unNameLen );
|
||||
|
||||
uint32 unDataLen = (uint32)Q_URLDecode( (char*)bufParamValue.Base(), bufParamValue.TellPut(), (const char*)bufParamValue.Base(), bufParamValue.TellPut() );
|
||||
pParam->set_value( (const uint8*)bufParamValue.Base(), unDataLen );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Gets a singleton buffer pool for HTTP responses
|
||||
//----------------------------------------------------------------------------
|
||||
static GCConVar http_response_max_pool_size_mb( "http_response_max_pool_size_mb", "10", "Maximum size in bytes of the HTTP Response buffer pool" );
|
||||
static GCConVar http_response_init_buffer_size( "http_response_init_buffer_size", "65536", "Initial buffer size for buffers in the HTTP Response buffer pool" );
|
||||
/*static*/ CBufferPool &CHTTPResponse::GetBufferPool()
|
||||
{
|
||||
static CBufferPool s_bufferPool( "HTTP Response", http_response_max_pool_size_mb, http_response_init_buffer_size, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::CONTAINS_CRLF );
|
||||
return s_bufferPool;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Construct a response object, it defaults to being a 500 internal server error response
|
||||
//-----------------------------------------------------------------------------
|
||||
CHTTPResponse::CHTTPResponse() :
|
||||
m_pbufBody( GetBufferPool().GetBuffer() ),
|
||||
m_eStatusCode( k_EHTTPStatusCode500InternalServerError )
|
||||
{
|
||||
m_pkvResponseHeaders = new KeyValues( "ResponseHeaders" );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CHTTPResponse::~CHTTPResponse()
|
||||
{
|
||||
GetBufferPool().ReturnBuffer( m_pbufBody );
|
||||
m_pbufBody = NULL;
|
||||
|
||||
if ( m_pkvResponseHeaders )
|
||||
m_pkvResponseHeaders->deleteThis();
|
||||
|
||||
m_pkvResponseHeaders = NULL;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Sets the "expiration" response header to a given number of seconds
|
||||
// ahead or behind the current time. You can set the Expires header directly as well,
|
||||
// this is just a helper so you don't have to deal with time formatting.
|
||||
//-----------------------------------------------------------------------------
|
||||
void CHTTPResponse::SetExpirationHeaderDeltaFromNow( int32 nSecondsFromNow )
|
||||
{
|
||||
time_t rawtime;
|
||||
time( &rawtime );
|
||||
rawtime += nSecondsFromNow;
|
||||
SetHeaderTimeValue( "expires", rawtime );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Formats a time value and sets it as a header. This is a helper so
|
||||
// you don't have to deal with time formatting
|
||||
//-----------------------------------------------------------------------------
|
||||
void CHTTPResponse::SetHeaderTimeValue( const char *pchHeaderName, RTime32 rtTimestamp )
|
||||
{
|
||||
char rgchDate[128];
|
||||
struct tm tmStruct;
|
||||
time_t rawtime = rtTimestamp;
|
||||
Plat_gmtime( &rawtime, &tmStruct );
|
||||
DbgVerify( strftime( rgchDate, 128, "%a, %d %b %Y %H:%M:%S GMT", &tmStruct ) );
|
||||
SetResponseHeaderValue( pchHeaderName, rgchDate );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Serializes the response into a message object (for proxying between
|
||||
// back-end Steam servers).
|
||||
//-----------------------------------------------------------------------------
|
||||
void CHTTPResponse::SerializeIntoProtoBuf( CMsgHttpResponse & response ) const
|
||||
{
|
||||
MEM_ALLOC_CREDIT();
|
||||
response.set_status_code( m_eStatusCode );
|
||||
|
||||
FOR_EACH_VALUE( m_pkvResponseHeaders, pkvRequestHeader )
|
||||
{
|
||||
const char *pchName = pkvRequestHeader->GetName();
|
||||
const char *pchValue = pkvRequestHeader->GetString();
|
||||
|
||||
if ( pchName && pchValue )
|
||||
{
|
||||
CMsgHttpResponse_ResponseHeader *pHeader = response.add_headers();
|
||||
pHeader->set_name( pchName );
|
||||
pHeader->set_value( pchValue );
|
||||
}
|
||||
}
|
||||
|
||||
if( m_pbufBody->TellPut() > 0 )
|
||||
response.set_body( m_pbufBody->Base(), m_pbufBody->TellPut() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Deserializes the response from a message object (for proxying between
|
||||
// back-end Steam servers).
|
||||
//-----------------------------------------------------------------------------
|
||||
void CHTTPResponse::DeserializeFromProtoBuf( const CMsgHttpResponse & response )
|
||||
{
|
||||
m_eStatusCode = (EHTTPStatusCode)response.status_code();
|
||||
|
||||
for( int i=0; i<response.headers_size(); i++ )
|
||||
{
|
||||
m_pkvResponseHeaders->SetString( response.headers(i).name().c_str(), response.headers(i).value().c_str() );
|
||||
}
|
||||
|
||||
if( response.has_body() )
|
||||
m_pbufBody->Put( response.body().data(), response.body().size() );
|
||||
}
|
||||
|
||||
+1451
File diff suppressed because it is too large
Load Diff
+1626
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Encapsultes the job system's version of time (which is != to wall clock time)
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
uint64 CJobTime::sm_lTimeCur = 0;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CJobTime::CJobTime()
|
||||
{
|
||||
m_lTime = sm_lTimeCur;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Sets our value to the current time
|
||||
//-----------------------------------------------------------------------------
|
||||
void CJobTime::SetToJobTime()
|
||||
{
|
||||
m_lTime = sm_lTimeCur;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Sets our value as an offset from the current time
|
||||
//-----------------------------------------------------------------------------
|
||||
void CJobTime::SetFromJobTime( int64 dMicroSecOffset )
|
||||
{
|
||||
m_lTime = sm_lTimeCur + dMicroSecOffset;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns the amount of time that's passed between our time and the
|
||||
// current time.
|
||||
// Output: Time that's passed between our time and the current time
|
||||
//-----------------------------------------------------------------------------
|
||||
int64 CJobTime::CServerMicroSecsPassed() const
|
||||
{
|
||||
return( sm_lTimeCur - m_lTime );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Updates our current time value. We only
|
||||
// update the time once per frame-- the rest of the time, we just
|
||||
// access a cached copy of the time. On the server, we use an arbitrary
|
||||
// time value beginning at 0, and incremented by 50 milliseconds every
|
||||
// frame.
|
||||
// NOTE: This should only be called once per frame.
|
||||
//-----------------------------------------------------------------------------
|
||||
void CJobTime::UpdateJobTime( int cMicroSecPerShellFrame )
|
||||
{
|
||||
sm_lTimeCur += cMicroSecPerShellFrame; // force a 50 msec clock
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Stomps the current clock with the specified time
|
||||
// Input : lCurrentTime - new current time
|
||||
//-----------------------------------------------------------------------------
|
||||
void CJobTime::SetCurrentJobTime( uint64 lCurrentTime )
|
||||
{
|
||||
sm_lTimeCur = lCurrentTime;
|
||||
}
|
||||
|
||||
} // namespace GCSDK
|
||||
@@ -0,0 +1,673 @@
|
||||
//====== Copyright , Valve Corporation, All rights reserved. =======
|
||||
//
|
||||
// Purpose: Provides names for GC message types
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: allow global initializers for a list of message infos. The message
|
||||
// list will assemble them all into a single list when it initializes.
|
||||
//-----------------------------------------------------------------------------
|
||||
class CMessageListRegistration
|
||||
{
|
||||
public:
|
||||
CMessageListRegistration( MsgInfo_t *pMsgInfo, int cMsgInfo, void *pExtra = NULL );
|
||||
|
||||
static CMessageListRegistration *sm_pFirst;
|
||||
CMessageListRegistration *m_pNext;
|
||||
MsgInfo_t *m_pMsgInfo;
|
||||
int m_cMsgInfo;
|
||||
};
|
||||
|
||||
|
||||
DECLARE_GC_EMIT_GROUP( g_EGMessages, messages );
|
||||
|
||||
//function called when a message is tallied but not registered properly. Used to help consolidate behavior for untracked messages
|
||||
static void HandleUntrackedMsg( const char* pszMsgOperation, uint32 nMsgID )
|
||||
{
|
||||
//for now output this as verbose so that we can clean up the initial spam. Then promote this to warnings in the future
|
||||
EG_VERBOSE( g_EGMessages, "Found %s message with ID %d that was not properly registered. Unable to tally information\n", pszMsgOperation, nMsgID );
|
||||
}
|
||||
|
||||
|
||||
CMessageListRegistration::CMessageListRegistration( MsgInfo_t *pMsgInfo, int cMsgInfo, void *pExtra )
|
||||
: m_pMsgInfo( pMsgInfo ),
|
||||
m_cMsgInfo( cMsgInfo )
|
||||
{
|
||||
m_pNext = sm_pFirst;
|
||||
sm_pFirst = this;
|
||||
}
|
||||
|
||||
CMessageListRegistration *CMessageListRegistration::sm_pFirst = NULL;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns the name of a message type
|
||||
//-----------------------------------------------------------------------------
|
||||
const char *PchMsgNameFromEMsg( MsgType_t eMsg )
|
||||
{
|
||||
const char *pchMsgName = k_rgchUnknown;
|
||||
g_theMessageList.GetMessage( eMsg, &pchMsgName, MT_GC );
|
||||
return pchMsgName;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void MsgRegistrationFromEnumDescriptor( const ::google::protobuf::EnumDescriptor *pEnumDescriptor, int nTypeMask )
|
||||
{
|
||||
// build the struct list for messages
|
||||
MsgInfo_t *pMsgInfos = new MsgInfo_t[ pEnumDescriptor->value_count() ];
|
||||
memset( pMsgInfos, 0, sizeof( MsgInfo_t ) * pEnumDescriptor->value_count() );
|
||||
|
||||
for ( int i = 0; i < pEnumDescriptor->value_count(); ++i )
|
||||
{
|
||||
const ::google::protobuf::EnumValueDescriptor *pEnumValueDescriptor = pEnumDescriptor->value(i);
|
||||
pMsgInfos[ i ].eMsg = pEnumValueDescriptor->number();
|
||||
pMsgInfos[ i ].pchMsgName = pEnumValueDescriptor->name().c_str();
|
||||
pMsgInfos[ i ].nFlags = nTypeMask;
|
||||
}
|
||||
|
||||
new CMessageListRegistration( pMsgInfos, pEnumDescriptor->value_count() );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
CMessageList g_theMessageList;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// CMessageList
|
||||
//
|
||||
// builds a hash of the MsgInfo_t table so that information about messages
|
||||
// can be found without searching.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
CMessageList::CMessageList( ) : m_bProfiling( false ), m_ulProfileMicrosecs( 0 )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// CMessageList
|
||||
//
|
||||
// builds a hash of the MsgInfo_t table so that information about messages
|
||||
// can be found without searching.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CMessageList::BInit( )
|
||||
{
|
||||
m_bProfiling = false;
|
||||
m_ulProfileMicrosecs = 0;
|
||||
|
||||
// figure out our message count
|
||||
int cMessageInfos = 0;
|
||||
for( CMessageListRegistration *pReg = CMessageListRegistration::sm_pFirst; pReg != NULL; pReg = pReg->m_pNext)
|
||||
{
|
||||
cMessageInfos += pReg->m_cMsgInfo;
|
||||
}
|
||||
|
||||
// message indexes should fit in a short
|
||||
Assert( cMessageInfos < SHRT_MAX );
|
||||
|
||||
m_vecMsgInfo.EnsureCapacity( cMessageInfos );
|
||||
m_vecMsgInfo.RemoveAll();
|
||||
m_vecMessageInfoBuckets.RemoveAll();
|
||||
|
||||
int nIndex = 0;
|
||||
for( CMessageListRegistration *pReg = CMessageListRegistration::sm_pFirst; pReg != NULL; pReg = pReg->m_pNext)
|
||||
{
|
||||
for ( int nRegIndex = 0; nRegIndex < pReg->m_cMsgInfo; nRegIndex++ )
|
||||
{
|
||||
nIndex = m_vecMsgInfo.AddToTail( pReg->m_pMsgInfo[nRegIndex] );
|
||||
|
||||
int nSlot;
|
||||
int nBucket = HashMessage( pReg->m_pMsgInfo[nRegIndex].eMsg, nSlot );
|
||||
|
||||
AssureBucket( nBucket );
|
||||
|
||||
if ( m_vecMessageInfoBuckets[nBucket][nSlot] != -1 )
|
||||
{
|
||||
int otherIndex = m_vecMessageInfoBuckets[nBucket][nSlot];
|
||||
MsgInfo_t &otherMsg = m_vecMsgInfo[ otherIndex ];
|
||||
AssertFatalMsg2( false, "Message collision: %s redefined as %s",
|
||||
otherMsg.pchMsgName,
|
||||
pReg->m_pMsgInfo[nRegIndex].pchMsgName);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_vecMessageInfoBuckets[nBucket][nSlot] = (short) nIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//start our window
|
||||
ResetWindow();
|
||||
//and start our global timer
|
||||
m_sCollectTime[ MsgInfo_t::k_EStatsGroupGlobal ].SetToJobTime();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Tally the sending of a message
|
||||
//-----------------------------------------------------------------------------
|
||||
void CMessageList::TallySendMessage( MsgType_t eMsgType, uint32 unMsgSize, uint32 nSourceMask )
|
||||
{
|
||||
short nIndex = GetMessageIndex( eMsgType );
|
||||
if ( nIndex == - 1 )
|
||||
{
|
||||
HandleUntrackedMsg( "send", eMsgType );
|
||||
return;
|
||||
}
|
||||
|
||||
TallyMessageInternal( m_vecMsgInfo[nIndex],MsgInfo_t::k_EStatsTypeSent, unMsgSize, nSourceMask );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Tally the receiving of a message
|
||||
//-----------------------------------------------------------------------------
|
||||
void CMessageList::TallyReceiveMessage( MsgType_t eMsgType, uint32 unMsgSize, uint32 nSourceMask )
|
||||
{
|
||||
short nIndex = GetMessageIndex( eMsgType );
|
||||
if ( nIndex == - 1 )
|
||||
{
|
||||
HandleUntrackedMsg( "receive", eMsgType );
|
||||
return;
|
||||
}
|
||||
|
||||
TallyMessageInternal( m_vecMsgInfo[nIndex], MsgInfo_t::k_EStatsTypeReceived, unMsgSize, nSourceMask );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Tally the receiving of a message
|
||||
//-----------------------------------------------------------------------------
|
||||
void CMessageList::TallyMultiplexedMessage( MsgType_t eMsgType, uint32 uSent, uint32 cRecipients, uint32 uMsgSize, uint32 nSourceMask )
|
||||
{
|
||||
short nIndex = GetMessageIndex( eMsgType );
|
||||
if ( nIndex == - 1 )
|
||||
{
|
||||
HandleUntrackedMsg( "multiplex", eMsgType );
|
||||
return;
|
||||
}
|
||||
|
||||
MsgInfo_t &msgInfo = m_vecMsgInfo[nIndex];
|
||||
TallyMessageInternal( msgInfo, MsgInfo_t::k_EStatsTypeMultiplexedSends, uSent, nSourceMask, 1 );
|
||||
TallyMessageInternal( msgInfo, MsgInfo_t::k_EStatsTypeMultiplexedSendsRaw, uMsgSize * cRecipients, nSourceMask, cRecipients );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Tally the receiving of a message
|
||||
//-----------------------------------------------------------------------------
|
||||
void CMessageList::TallyMessageInternal( MsgInfo_t &msgInfo, MsgInfo_t::EStatsType eBucket, uint32 unMsgSize, uint32 nSourceMask, uint32 cMessages )
|
||||
{
|
||||
//log this for global stats
|
||||
msgInfo.stats[ MsgInfo_t::k_EStatsGroupGlobal ][ eBucket ].nCount += cMessages;
|
||||
msgInfo.stats[ MsgInfo_t::k_EStatsGroupGlobal ][ eBucket ].uBytes += unMsgSize;
|
||||
msgInfo.stats[ MsgInfo_t::k_EStatsGroupGlobal ][ eBucket ].nSourceMask |= nSourceMask;
|
||||
|
||||
//track this in our current timing window
|
||||
msgInfo.stats[ MsgInfo_t::k_EStatsGroupWindow ][ eBucket ].nCount += cMessages;
|
||||
msgInfo.stats[ MsgInfo_t::k_EStatsGroupWindow ][ eBucket ].uBytes += unMsgSize;
|
||||
msgInfo.stats[ MsgInfo_t::k_EStatsGroupWindow ][ eBucket ].nSourceMask |= nSourceMask;
|
||||
|
||||
//track our window totals as well
|
||||
m_WindowTotals[ eBucket ].nCount += cMessages;
|
||||
m_WindowTotals[ eBucket ].uBytes += unMsgSize;
|
||||
m_WindowTotals[ eBucket ].nSourceMask |= nSourceMask;
|
||||
|
||||
//and if we are profiling, track the data into our profiling window
|
||||
if ( m_bProfiling )
|
||||
{
|
||||
msgInfo.stats[ MsgInfo_t::k_EStatsGroupProfile ][ eBucket ].nCount += cMessages;
|
||||
msgInfo.stats[ MsgInfo_t::k_EStatsGroupProfile ][ eBucket ].uBytes += unMsgSize;
|
||||
msgInfo.stats[ MsgInfo_t::k_EStatsGroupProfile ][ eBucket ].nSourceMask |= nSourceMask;
|
||||
}
|
||||
}
|
||||
|
||||
//called to obtain the totals for the timing window
|
||||
const MsgInfo_t::Stats_t& CMessageList::GetWindowTotal( MsgInfo_t::EStatsType eType ) const
|
||||
{
|
||||
return m_WindowTotals[ eType ];
|
||||
}
|
||||
|
||||
//called to reset the window timings
|
||||
void CMessageList::ResetWindow()
|
||||
{
|
||||
//reset when our window started
|
||||
m_sCollectTime[ MsgInfo_t::k_EStatsGroupWindow ].SetToJobTime();
|
||||
|
||||
// starting a new window... clear everything
|
||||
FOR_EACH_VEC( m_vecMsgInfo, nIndex )
|
||||
{
|
||||
for( uint32 nType = 0; nType < MsgInfo_t::k_EStatsType_Count; nType++ )
|
||||
{
|
||||
m_vecMsgInfo[ nIndex ].stats[ MsgInfo_t::k_EStatsGroupWindow ][ nType ].nCount = 0;
|
||||
m_vecMsgInfo[ nIndex ].stats[ MsgInfo_t::k_EStatsGroupWindow ][ nType ].uBytes = 0;
|
||||
m_vecMsgInfo[ nIndex ].stats[ MsgInfo_t::k_EStatsGroupWindow ][ nType ].nSourceMask = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//and clear our totals
|
||||
for( uint32 nType = 0; nType < MsgInfo_t::k_EStatsType_Count; nType++ )
|
||||
{
|
||||
m_WindowTotals[ nType ].nCount = 0;
|
||||
m_WindowTotals[ nType ].uBytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Turns snapshot profiling on and off
|
||||
//-----------------------------------------------------------------------------
|
||||
void CMessageList::EnableProfiling( bool bEnableProfiling )
|
||||
{
|
||||
m_bProfiling = bEnableProfiling;
|
||||
if ( m_bProfiling )
|
||||
{
|
||||
m_sCollectTime[ MsgInfo_t::k_EStatsGroupProfile ].SetToJobTime();
|
||||
|
||||
// starting a new profile... clear everything
|
||||
FOR_EACH_VEC( m_vecMsgInfo, nIndex )
|
||||
{
|
||||
for( uint32 nType = 0; nType < MsgInfo_t::k_EStatsType_Count; nType++ )
|
||||
{
|
||||
m_vecMsgInfo[ nIndex ].stats[ MsgInfo_t::k_EStatsGroupProfile ][ nType ].nCount = 0;
|
||||
m_vecMsgInfo[ nIndex ].stats[ MsgInfo_t::k_EStatsGroupProfile ][ nType ].uBytes = 0;
|
||||
m_vecMsgInfo[ nIndex ].stats[ MsgInfo_t::k_EStatsGroupProfile ][ nType ].nSourceMask = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ulProfileMicrosecs = m_sCollectTime[ MsgInfo_t::k_EStatsGroupProfile ].CServerMicroSecsPassed();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint64 CMessageList::GetGroupDuration( MsgInfo_t::EStatsGroup eGroup ) const
|
||||
{
|
||||
//handle the special case of it being a profile, where if we are no longer profiling, we want to use our cached value
|
||||
if( ( eGroup == MsgInfo_t::k_EStatsGroupProfile ) && !m_bProfiling )
|
||||
{
|
||||
return m_ulProfileMicrosecs;
|
||||
}
|
||||
|
||||
//otherwise we can just use the timer directly
|
||||
return m_sCollectTime[ eGroup ].CServerMicroSecsPassed();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// print statistics bout each message we handle
|
||||
//-----------------------------------------------------------------------------
|
||||
void CMessageList::PrintStats( bool bShowAll, bool bSortByFrequency, MsgInfo_t::EStatsGroup eGroup, MsgInfo_t::EStatsType eType, uint32 nSourceMask ) const
|
||||
{
|
||||
// Figure out which time value we should use for rate calcs
|
||||
uint64 ulMicroseconds = GetGroupDuration( eGroup );
|
||||
|
||||
// work out a sorted list
|
||||
CUtlMap<uint64, uint32> mapValueToMsg( DefLessFunc( uint64 ) );
|
||||
uint64 unTotalBytes = 0;
|
||||
uint32 unTotalMessages = 0;
|
||||
|
||||
FOR_EACH_VEC( m_vecMsgInfo, n )
|
||||
{
|
||||
//see if we are looking for a particular source, and if so, if this has that source set
|
||||
if( nSourceMask && ( ( m_vecMsgInfo[n].stats[ eGroup ][ eType ].nSourceMask & nSourceMask ) == 0 ) )
|
||||
continue;
|
||||
|
||||
uint32 nCount = m_vecMsgInfo[n].stats[ eGroup ][ eType ].nCount;
|
||||
uint64 uBytes = m_vecMsgInfo[n].stats[ eGroup ][ eType ].uBytes;
|
||||
|
||||
unTotalMessages += nCount;
|
||||
unTotalBytes += uBytes;
|
||||
|
||||
if ( nCount == 0 && !bShowAll )
|
||||
continue;
|
||||
|
||||
mapValueToMsg.Insert( bSortByFrequency ? (uint64)nCount : uBytes, n );
|
||||
}
|
||||
|
||||
double fSeconds = ulMicroseconds / ( 1000.0 * 1000.0 );
|
||||
|
||||
// 5, 46, 10, 6, 14, 6, 10, 10
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%s", "EMsg MessageName Count % Bytes % MsgPS Avg/Msg KBPS\n" );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%s", "----- ---------------------------------------------- ---------- ------ -------------- ------ --------- ---------- ----------\n" );
|
||||
|
||||
for ( uint16 iValue = mapValueToMsg.LastInorder(); iValue != mapValueToMsg.InvalidIndex(); iValue = mapValueToMsg.PrevInorder( iValue ) )
|
||||
{
|
||||
uint32 n = mapValueToMsg[iValue];
|
||||
|
||||
//see if we are looking for a particular source, and if so, if this has that source set
|
||||
if( nSourceMask && ( ( m_vecMsgInfo[n].stats[ eGroup ][ eType ].nSourceMask & nSourceMask ) == 0 ) )
|
||||
continue;
|
||||
|
||||
uint32 nCount = m_vecMsgInfo[n].stats[ eGroup ][ eType].nCount;
|
||||
uint64 uBytes = m_vecMsgInfo[n].stats[ eGroup ][ eType].uBytes;
|
||||
|
||||
uint32 uAvgMsg = 0;
|
||||
if ( nCount > 0 )
|
||||
uAvgMsg = uBytes / nCount;
|
||||
|
||||
float flCountPerc = 0;
|
||||
if ( unTotalMessages > 0 )
|
||||
flCountPerc = nCount * 100.0f / unTotalMessages;
|
||||
float flBytesPerc = 0;
|
||||
if ( unTotalBytes > 0 )
|
||||
flBytesPerc = uBytes * 100.0f / unTotalBytes;
|
||||
double fMsgPS = 0.0;
|
||||
if ( ulMicroseconds > 0 )
|
||||
fMsgPS = nCount / fSeconds;
|
||||
double fKBPS = 0.0f;
|
||||
if ( ulMicroseconds > 0 )
|
||||
fKBPS = uBytes / ( fSeconds * 1000.0 );
|
||||
|
||||
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%5u %-46s %10u %5.1f%% %14llu %5.1f%% %9.1f %10u %10.2f\n",
|
||||
m_vecMsgInfo[n].eMsg,
|
||||
m_vecMsgInfo[n].pchMsgName,
|
||||
nCount,
|
||||
flCountPerc,
|
||||
uBytes,
|
||||
flBytesPerc,
|
||||
fMsgPS,
|
||||
uAvgMsg,
|
||||
fKBPS );
|
||||
}
|
||||
|
||||
uint32 uAvgMsgTotal = 0;
|
||||
if ( unTotalMessages > 0 )
|
||||
uAvgMsgTotal = unTotalBytes / unTotalMessages;
|
||||
double fMsgPS = 0.0;
|
||||
if ( ulMicroseconds > 0 )
|
||||
fMsgPS = unTotalMessages / fSeconds;
|
||||
double fKBPSTotal = 0.0;
|
||||
if ( ulMicroseconds > 0 )
|
||||
fKBPSTotal = unTotalBytes / ( fSeconds * 1000.0 );
|
||||
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "----- ---------------------------------------------- ---------- ------ -------------- ------ --------- ---------- ----------\n" );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Totals %10u 100.0%% %14llu 100.0%% %9.1f %10u %10.2f\n",
|
||||
unTotalMessages,
|
||||
unTotalBytes,
|
||||
fMsgPS,
|
||||
uAvgMsgTotal,
|
||||
fKBPSTotal );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// print statistics bout each message we handle
|
||||
//-----------------------------------------------------------------------------
|
||||
void CMessageList::PrintMultiplexStats( MsgInfo_t::EStatsGroup eGroup, bool bSortByFrequency, uint32 nSourceMask ) const
|
||||
{
|
||||
// Figure out which time value we should use for rate calcs
|
||||
uint64 ulMicroseconds = GetGroupDuration( eGroup );
|
||||
|
||||
MsgInfo_t::Stats_t rgTotals[MsgInfo_t::k_EStatsType_Count];
|
||||
|
||||
const MsgInfo_t::EStatsType eMultiType = MsgInfo_t::k_EStatsTypeMultiplexedSends;
|
||||
const MsgInfo_t::EStatsType eRawType = MsgInfo_t::k_EStatsTypeMultiplexedSendsRaw;
|
||||
|
||||
// work out a sorted list
|
||||
CUtlMap<uint64, uint32> mapValueToMsg( DefLessFunc( uint64 ) );
|
||||
FOR_EACH_VEC( m_vecMsgInfo, n )
|
||||
{
|
||||
const MsgInfo_t &msgInfo = m_vecMsgInfo[n];
|
||||
|
||||
//see if we are looking for a particular source, and if so, if this has that source set
|
||||
if( nSourceMask && ( msgInfo.stats[ eGroup ][ eMultiType ].nSourceMask & nSourceMask ) == 0 )
|
||||
continue;
|
||||
|
||||
rgTotals[eMultiType].nCount += msgInfo.stats[ eGroup ][ eMultiType ].nCount;
|
||||
rgTotals[eMultiType].uBytes += msgInfo.stats[ eGroup ][ eMultiType ].uBytes;
|
||||
rgTotals[eRawType].nCount += msgInfo.stats[ eGroup ][ eRawType ].nCount;
|
||||
rgTotals[eRawType].uBytes += msgInfo.stats[ eGroup ][ eRawType ].uBytes;
|
||||
|
||||
if ( msgInfo.stats[ eGroup ][ eMultiType ].nCount == 0 )
|
||||
continue;
|
||||
|
||||
uint32 nCount = msgInfo.stats[ eGroup ][ eMultiType ].nCount;
|
||||
uint64 uBytes = msgInfo.stats[ eGroup ][ eMultiType ].uBytes;
|
||||
mapValueToMsg.Insert( bSortByFrequency ? (uint64)nCount : uBytes, n );
|
||||
}
|
||||
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%s", "EMsg MessageName Count % KB % Avg/Msg KBPS Msg Saved % KB Saved %\n" );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%s", "----- ---------------------------------------------- ---------- ------ -------------- ------ ---------- ---------- ---------- ------ -------------- ------\n" );
|
||||
|
||||
uint32 unTotalMessages = 0;
|
||||
uint64 unTotalBytes = 0;
|
||||
|
||||
for ( uint16 iValue = mapValueToMsg.LastInorder(); iValue != mapValueToMsg.InvalidIndex(); iValue = mapValueToMsg.PrevInorder( iValue ) )
|
||||
{
|
||||
const MsgInfo_t &msgInfo = m_vecMsgInfo[ mapValueToMsg[iValue] ];
|
||||
|
||||
//see if we are looking for a particular source, and if so, if this has that source set
|
||||
if( nSourceMask && ( msgInfo.stats[ eGroup ][ eMultiType ].nSourceMask & nSourceMask ) == 0 )
|
||||
continue;
|
||||
|
||||
uint32 nCount = msgInfo.stats[ eGroup ][ eMultiType ].nCount;
|
||||
uint64 uBytes = msgInfo.stats[ eGroup ][ eMultiType ].uBytes;
|
||||
|
||||
unTotalMessages += nCount;
|
||||
unTotalBytes += uBytes;
|
||||
|
||||
int32 nMessagesSaved = msgInfo.stats[ eGroup ][ eRawType ].nCount - msgInfo.stats[ eGroup ][ eMultiType ].nCount;
|
||||
int64 uBytesSaved = msgInfo.stats[ eGroup ][ eRawType ].uBytes - msgInfo.stats[ eGroup ][ eMultiType ].uBytes;
|
||||
|
||||
uint32 nMessagesIfNotMultiplex = msgInfo.stats[ eGroup ][ eRawType ].nCount;
|
||||
uint64 uBytesIfNotMultiplex = msgInfo.stats[ eGroup ][ eRawType ].uBytes;
|
||||
|
||||
uint32 uAvgMsg = 0;
|
||||
if ( nCount > 0 )
|
||||
uAvgMsg = uBytes / nCount;
|
||||
|
||||
float flCountPerc = 0;
|
||||
if ( rgTotals[eMultiType].nCount > 0 )
|
||||
flCountPerc = nCount * 100.0f / rgTotals[eMultiType].nCount;
|
||||
float flBytesPerc = 0;
|
||||
if ( rgTotals[eMultiType].uBytes > 0 )
|
||||
flBytesPerc = uBytes * 100.0f / rgTotals[eMultiType].uBytes;
|
||||
float fKBPS = 0.0f;
|
||||
if ( ulMicroseconds > 0 )
|
||||
fKBPS = uBytes * 1000.0f / ulMicroseconds;
|
||||
|
||||
float flMessagesSavedPerc = 0.0f;
|
||||
if ( nMessagesIfNotMultiplex > 0 )
|
||||
flMessagesSavedPerc = nMessagesSaved * 100.0f / nMessagesIfNotMultiplex;
|
||||
float flBytesSavedPerc = 0.0f;
|
||||
if ( uBytesIfNotMultiplex > 0 )
|
||||
flBytesSavedPerc = uBytesSaved * 100.0f / uBytesIfNotMultiplex;
|
||||
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%5u %-46s %10u %5.1f%% %14llu %5.1f%% %10u %10.2f %10d %5.1f%% %14lld %5.1f%%\n",
|
||||
msgInfo.eMsg,
|
||||
msgInfo.pchMsgName,
|
||||
nCount,
|
||||
flCountPerc,
|
||||
uBytes / 1024,
|
||||
flBytesPerc,
|
||||
uAvgMsg,
|
||||
fKBPS,
|
||||
nMessagesSaved,
|
||||
flMessagesSavedPerc,
|
||||
uBytesSaved / 1024,
|
||||
flBytesSavedPerc );
|
||||
}
|
||||
|
||||
uint32 uAvgMsgTotal = 0;
|
||||
if ( unTotalMessages > 0 )
|
||||
uAvgMsgTotal = unTotalBytes / unTotalMessages;
|
||||
|
||||
float fKBPSTotal = 0.0f;
|
||||
if ( ulMicroseconds > 0 )
|
||||
fKBPSTotal = unTotalBytes * 1000.0f / ulMicroseconds;
|
||||
|
||||
float flTotalMessagesPct = 0.0f;
|
||||
if ( rgTotals[eMultiType].nCount > 0 )
|
||||
flTotalMessagesPct = unTotalMessages * 100.0f / rgTotals[eMultiType].nCount;
|
||||
|
||||
float flTotalBytesPct = 0.0f;
|
||||
if ( rgTotals[eMultiType].uBytes > 0 )
|
||||
flTotalBytesPct = unTotalBytes * 100.0f / rgTotals[eMultiType].uBytes;
|
||||
|
||||
int32 nTotalMessagesSaved = rgTotals[eRawType].nCount - rgTotals[eMultiType].nCount;
|
||||
int64 nTotalBytesSaved = rgTotals[eRawType].uBytes - rgTotals[eMultiType].uBytes;
|
||||
|
||||
int32 nTotalMessagesIfNotMultiplex = unTotalMessages - rgTotals[eMultiType].nCount + rgTotals[eRawType].nCount;
|
||||
int64 uTotalBytesIfNotMultiplex = unTotalBytes - rgTotals[eMultiType].uBytes + rgTotals[eRawType].uBytes;
|
||||
|
||||
float flTotalMessagesSavedPct = 0.0f;
|
||||
if ( nTotalMessagesIfNotMultiplex > 0 )
|
||||
flTotalMessagesSavedPct = nTotalMessagesSaved * 100.0f / nTotalMessagesIfNotMultiplex;
|
||||
|
||||
float flTotalBytesSavedPct = 0.0f;
|
||||
if ( uTotalBytesIfNotMultiplex > 0 )
|
||||
flTotalBytesSavedPct = nTotalBytesSaved * 100.0f / uTotalBytesIfNotMultiplex;
|
||||
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "----- ---------------------------------------------- ---------- ------ -------------- ------ ---------- ---------- ---------- ------ -------------- ------\n" );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Totals %10u %5.1f%% %14llu %5.1f%% %10u %10.2f %10d %5.1f%% %14lld %5.1f%%\n",
|
||||
unTotalMessages,
|
||||
flTotalMessagesPct,
|
||||
unTotalBytes / 1024,
|
||||
flTotalBytesPct,
|
||||
uAvgMsgTotal,
|
||||
fKBPSTotal,
|
||||
nTotalMessagesSaved,
|
||||
flTotalMessagesSavedPct,
|
||||
nTotalBytesSaved / 1024,
|
||||
flTotalBytesSavedPct );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Destroys a message list by deallocating everything it's got
|
||||
//-----------------------------------------------------------------------------
|
||||
CMessageList::~CMessageList()
|
||||
{
|
||||
int nUsedBuckets = 0;
|
||||
for ( int nIndex = 0; nIndex < m_vecMessageInfoBuckets.Count(); nIndex++ )
|
||||
{
|
||||
if ( m_vecMessageInfoBuckets[nIndex] != NULL )
|
||||
{
|
||||
FreePv( m_vecMessageInfoBuckets[nIndex] );
|
||||
m_vecMessageInfoBuckets[nIndex] = NULL;
|
||||
nUsedBuckets++;
|
||||
}
|
||||
}
|
||||
|
||||
m_vecMessageInfoBuckets.RemoveAll();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// assure that we've got the slots in the given bucket allocated
|
||||
//-----------------------------------------------------------------------------
|
||||
void CMessageList::AssureBucket( int nBucket )
|
||||
{
|
||||
// if this bucket is bigger then the array, extend the array
|
||||
if ( nBucket >= m_vecMessageInfoBuckets.Count() )
|
||||
{
|
||||
int nOldCount = m_vecMessageInfoBuckets.Count();
|
||||
// message ID "clumps" are usually 100 apart, so we'll try
|
||||
// to grow by 100 each time. Divide 100 by the bucket size and add
|
||||
// one for truncation to figure out our grow-by.
|
||||
int nNewCount = nBucket + (1 + (100 / m_kcBucketSize));
|
||||
|
||||
// get that count
|
||||
m_vecMessageInfoBuckets.EnsureCount( nNewCount );
|
||||
|
||||
// initialize the new ones to NULL
|
||||
for ( int nIndex = nOldCount; nIndex < nNewCount; nIndex++ )
|
||||
{
|
||||
m_vecMessageInfoBuckets[nIndex] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// is the bucket we want allocated?
|
||||
if ( m_vecMessageInfoBuckets[nBucket] == NULL )
|
||||
{
|
||||
// nope; get one and initialize it
|
||||
m_vecMessageInfoBuckets[nBucket] = (short*) PvAlloc( sizeof(short) * m_kcBucketSize );
|
||||
for ( int nIndex = 0; nIndex < m_kcBucketSize; nIndex++)
|
||||
m_vecMessageInfoBuckets[nBucket][nIndex] = -1;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Hash an MsgType_t and return the index into m_vecMsgInfo where it can be found
|
||||
// returns -1 if the MsgType_t is unknown
|
||||
//-----------------------------------------------------------------------------
|
||||
short CMessageList::GetMessageIndex( MsgType_t eMsg )
|
||||
{
|
||||
// find the slot and bucket for this message in our hash
|
||||
int nSlot;
|
||||
int nBucket = HashMessage( eMsg, nSlot );
|
||||
|
||||
// taller than the hash?
|
||||
if ( nBucket >= m_vecMessageInfoBuckets.Count() )
|
||||
return -1;
|
||||
|
||||
// not a bucket?
|
||||
if ( m_vecMessageInfoBuckets[nBucket] == NULL )
|
||||
return -1;
|
||||
|
||||
// get the index back to the global array
|
||||
short nIndex = m_vecMessageInfoBuckets[nBucket][nSlot];
|
||||
return nIndex;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Tests to see if a message is valid; if so, return a pointer to its name.
|
||||
// A message must match at least one of the nTypeMask flags to be considered
|
||||
// valid. If provided, ppMsgName is set to k_rgchUnknown if no match,
|
||||
// or the message name if matched.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool CMessageList::GetMessage( MsgType_t eMsg, const char **ppMsgName, int nTypeMask )
|
||||
{
|
||||
VPROF_BUDGET( "GetMessage", VPROF_BUDGETGROUP_JOBS_COROUTINES );
|
||||
|
||||
// if an out variable for the name is provided,
|
||||
// initialize it with a pointer to "unknown" string
|
||||
if ( ppMsgName != NULL )
|
||||
*ppMsgName = k_rgchUnknown;
|
||||
|
||||
short nIndex = GetMessageIndex( eMsg );
|
||||
|
||||
// good index?
|
||||
if ( nIndex == -1 )
|
||||
{
|
||||
if ( ppMsgName != NULL )
|
||||
{
|
||||
*ppMsgName = "Unknown MsgType - Not Found";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const MsgInfo_t &msgInfo = m_vecMsgInfo[nIndex];
|
||||
|
||||
// get the string out
|
||||
if ( ppMsgName != NULL )
|
||||
*ppMsgName = msgInfo.pchMsgName;
|
||||
|
||||
// it's good if it matches the flags
|
||||
return ( 0 != ( msgInfo.nFlags & nTypeMask ) );
|
||||
}
|
||||
|
||||
|
||||
} // namespace GCSDK
|
||||
|
||||
@@ -0,0 +1,466 @@
|
||||
//====== Copyright © 1996-2004, Valve Corporation, All rights reserved. =======
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#include "stdafx.h"
|
||||
#include <limits.h>
|
||||
#include "msgprotobuf.h"
|
||||
|
||||
#include "tier0/memdbgoff.h"
|
||||
|
||||
#ifdef GC
|
||||
namespace GCSDK
|
||||
{
|
||||
IMPLEMENT_CLASS_MEMPOOL( CProtoBufNetPacket, 1000, UTLMEMORYPOOL_GROW_SLOW );
|
||||
}
|
||||
#endif
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
// Global pool for protobuf header allocations
|
||||
CProtoBufMsgMemoryPoolMgr *GProtoBufMsgMemoryPoolMgr()
|
||||
{
|
||||
static CProtoBufMsgMemoryPoolMgr s_ProtoBufMsgMemoryPoolMgr;
|
||||
return &s_ProtoBufMsgMemoryPoolMgr;
|
||||
}
|
||||
|
||||
CThreadMutex CProtoBufMsgBase::s_PoolRegMutex;
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CProtoBufNetPacket::CProtoBufNetPacket( CNetPacket *pNetPacket, GCProtoBufMsgSrc eMsgSrc, const CSteamID steamID, uint32 nGCDirIndex, MsgType_t msgType )
|
||||
: m_msgType( msgType ), m_steamID( steamID ), m_bIsValid( false )
|
||||
{
|
||||
#ifdef VALVE_BIG_ENDIAN
|
||||
ProtoBufMsgHeader_t * pHdr = (ProtoBufMsgHeader_t *)pNetPacket->PubData();
|
||||
pHdr->m_EMsgFlagged = LittleDWord( pHdr->m_EMsgFlagged );
|
||||
pHdr->m_cubProtoBufExtHdr = LittleDWord( pHdr->m_cubProtoBufExtHdr );
|
||||
#endif
|
||||
|
||||
m_pNetPacket = pNetPacket;
|
||||
m_pNetPacket->AddRef();
|
||||
|
||||
m_pHeader = GProtoBufMsgMemoryPoolMgr()->AllocProtoBufHdr();
|
||||
|
||||
// Pull the length of the header out of the packet, but then validate it's not longer than the packet itself (ie, corrupt packet)
|
||||
const uint32 unLenProtoBufHeader = GetFixedHeader().m_cubProtoBufExtHdr;
|
||||
if ( unLenProtoBufHeader + sizeof( ProtoBufMsgHeader_t ) <= pNetPacket->CubData() )
|
||||
{
|
||||
if ( !m_pHeader->ParseFromArray( pNetPacket->PubData()+sizeof(ProtoBufMsgHeader_t), unLenProtoBufHeader ) )
|
||||
{
|
||||
AssertMsg4( false, "Failed parsing ProtoBuf extended header out of message: %s(%d) from %s size=%u", PchMsgNameFromEMsg( GetEMsg() ), GetEMsg(), steamID.Render(), pNetPacket->CubData() );
|
||||
}
|
||||
else
|
||||
{
|
||||
//if this packet doesn't have a source provided, we need to set the source of this message. If one is already provided, we should never stomp that data as that is the ultimate source of the message
|
||||
if( m_pHeader->gc_msg_src() == GCProtoBufMsgSrc_Unspecified )
|
||||
{
|
||||
//make sure to set the steam ID to what steam provided so clients can't spoof it
|
||||
m_pHeader->set_client_steam_id( steamID.ConvertToUint64() );
|
||||
//and track our original source type and which GC it was first received by
|
||||
m_pHeader->set_gc_dir_index_source( nGCDirIndex );
|
||||
m_pHeader->set_gc_msg_src( eMsgSrc );
|
||||
}
|
||||
|
||||
m_bIsValid = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertMsg4( false, "Unparseable protobuf message arrived: message %s(%u) from %s size=%u", PchMsgNameFromEMsg( GetEMsg() ), GetEMsg(), steamID.Render(), pNetPacket->CubData() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CProtoBufNetPacket::~CProtoBufNetPacket()
|
||||
{
|
||||
GProtoBufMsgMemoryPoolMgr()->FreeProtoBufHdr( m_pHeader );
|
||||
m_pNetPacket->Release();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets the body from the associated packet
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CProtoBufNetPacket::GetMsgBody( const uint8*& pubData, uint32& cubData ) const
|
||||
{
|
||||
if ( !IsValid() )
|
||||
{
|
||||
pubData = NULL;
|
||||
cubData = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
//when it initializes, it verifies the size, so we don't need to do validation here
|
||||
const uint32 nHdrOffset = sizeof( ProtoBufMsgHeader_t ) + GetFixedHeader().m_cubProtoBufExtHdr;
|
||||
pubData = PubData() + nHdrOffset;
|
||||
cubData = CubData() - nHdrOffset;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CProtoBufMsgMemoryPoolBase::CProtoBufMsgMemoryPoolBase( uint32 unTargetLow, uint32 unTargetHigh )
|
||||
{
|
||||
m_unTargetCountLow = unTargetLow;
|
||||
m_unTargetCountHigh = unTargetHigh;
|
||||
m_unAllocHitCounter = 0;
|
||||
m_unAllocMissCounter = 0;
|
||||
m_unAllocated = 0;
|
||||
m_pTSQueueFreeObjects = new CTSQueue< ::google::protobuf::Message * >();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CProtoBufMsgMemoryPoolBase::~CProtoBufMsgMemoryPoolBase()
|
||||
{
|
||||
AssertMsg( 0 == m_pTSQueueFreeObjects->Count(), "CProtoBufMsgMemoryPoolBase destructor called with items still in the queue. They must be freed by the derived class or they will be leaked" );
|
||||
|
||||
m_pTSQueueFreeObjects->Purge();
|
||||
delete m_pTSQueueFreeObjects;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Allocs a message from the pool
|
||||
//-----------------------------------------------------------------------------
|
||||
::google::protobuf::Message *CProtoBufMsgMemoryPoolBase::Alloc()
|
||||
{
|
||||
::google::protobuf::Message *pObject;
|
||||
if ( !m_pTSQueueFreeObjects->PopItem( &pObject ) || !pObject )
|
||||
{
|
||||
++m_unAllocMissCounter;
|
||||
pObject = InternalAlloc();
|
||||
}
|
||||
else
|
||||
{
|
||||
++m_unAllocHitCounter;
|
||||
uint32 unCount = m_pTSQueueFreeObjects->Count();
|
||||
|
||||
// We'll free an extra cached msg every alloc if we are over the higher limit, and every 6th if we
|
||||
// are over the lower limit. This allows some elasticity to peaks in demand.
|
||||
bool bFreeAnother = ( unCount > m_unTargetCountHigh )
|
||||
|| ( unCount > m_unTargetCountLow && m_unAllocHitCounter % 6 == 0 );
|
||||
|
||||
if ( bFreeAnother )
|
||||
{
|
||||
// Pop an extra item, so we can get down to target count over time
|
||||
::google::protobuf::Message *pThrowAway;
|
||||
if ( m_pTSQueueFreeObjects->PopItem( &pThrowAway ) && pThrowAway )
|
||||
{
|
||||
InternalFree( pThrowAway );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++m_unAllocated;
|
||||
return pObject;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns a message to the pool
|
||||
//-----------------------------------------------------------------------------
|
||||
void CProtoBufMsgMemoryPoolBase::Free( ::google::protobuf::Message *pObject )
|
||||
{
|
||||
// We always cache for re-use on free, though we may throw out later on alloc to shrink the pool
|
||||
pObject->Clear();
|
||||
|
||||
--m_unAllocated;
|
||||
m_pTSQueueFreeObjects->PushItem( pObject );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Counts the size of all the objects in the pool
|
||||
//-----------------------------------------------------------------------------
|
||||
uint32 CProtoBufMsgMemoryPoolBase::GetEstimatedSize()
|
||||
{
|
||||
// to fix a linux build break
|
||||
typedef ::google::protobuf::Message ProtoMsg_t;
|
||||
CUtlVector<ProtoMsg_t *> vecTemp;
|
||||
vecTemp.EnsureCapacity( m_pTSQueueFreeObjects->Count() * 2 );
|
||||
|
||||
// Should be "while (pObject != NULL)" because if we were actually using threads depending on count
|
||||
// would be bad. Unfortunately that gives me an infinite loop. See if this can be changed when this
|
||||
// is updated
|
||||
while ( m_pTSQueueFreeObjects->Count() > 0 )
|
||||
{
|
||||
::google::protobuf::Message *pObject = NULL;
|
||||
m_pTSQueueFreeObjects->PopItem( &pObject );
|
||||
vecTemp.AddToTail( pObject );
|
||||
}
|
||||
|
||||
uint32 unEstimate = 0;
|
||||
FOR_EACH_VEC( vecTemp, i )
|
||||
{
|
||||
unEstimate += vecTemp[i]->SpaceUsed();
|
||||
m_pTSQueueFreeObjects->PushItem( vecTemp[i] );
|
||||
}
|
||||
|
||||
// Scale the estimate by the number of objects outstanding
|
||||
float fScale = 1.0f;
|
||||
if ( vecTemp.Count() > 0 )
|
||||
{
|
||||
float fFree = (float)vecTemp.Count();
|
||||
fScale = ( fFree + (float)m_unAllocated ) / fFree;
|
||||
unEstimate *= fScale;
|
||||
}
|
||||
|
||||
return unEstimate;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Pops an item from the queue. To be used by the derived class
|
||||
// destructor to free the oustanding allocations
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CProtoBufMsgMemoryPoolBase::PopItem( google::protobuf::Message **ppMsg )
|
||||
{
|
||||
return m_pTSQueueFreeObjects->PopItem( ppMsg );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CProtoBufMsgMemoryPoolMgr::CProtoBufMsgMemoryPoolMgr() : m_PoolHeaders()
|
||||
{
|
||||
m_vecMsgPools.AddToTail( &m_PoolHeaders );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CProtoBufMsgMemoryPoolMgr::~CProtoBufMsgMemoryPoolMgr()
|
||||
{
|
||||
FOR_EACH_VEC( m_vecMsgPools, i )
|
||||
{
|
||||
if ( m_vecMsgPools[i] != &m_PoolHeaders )
|
||||
delete m_vecMsgPools[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Takes control of a message pool registered for a specific protobuf type
|
||||
//-----------------------------------------------------------------------------
|
||||
void CProtoBufMsgMemoryPoolMgr::RegisterPool( CProtoBufMsgMemoryPoolBase *pPool )
|
||||
{
|
||||
m_vecMsgPools.AddToTail( pPool );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Prints info on protobuf memory usage
|
||||
//-----------------------------------------------------------------------------
|
||||
void CProtoBufMsgMemoryPoolMgr::DumpPoolInfo()
|
||||
{
|
||||
uint32 unTotalSize = 0;
|
||||
Msg( "CProtoBufMsgMemoryPoolMgr:\n" );
|
||||
Msg( " PoolName Allocated Free Est. Size Hit Rate\n" );
|
||||
Msg( " ----------------------------------------- ----------- ----------- ----------- -----------\n" );
|
||||
FOR_EACH_VEC( m_vecMsgPools, i )
|
||||
{
|
||||
uint32 unHitCount = m_vecMsgPools[i]->GetAllocHitCount();
|
||||
uint32 unMissCount = m_vecMsgPools[i]->GetAllocMissCount();
|
||||
float flHitRate = 0.0f;
|
||||
if ( unHitCount > 0 || unMissCount > 0 )
|
||||
{
|
||||
flHitRate = (float)unHitCount / (float)(unHitCount+unMissCount);
|
||||
flHitRate *= 100.0f;
|
||||
}
|
||||
|
||||
uint32 unEstimate = m_vecMsgPools[i]->GetEstimatedSize();
|
||||
Msg( "%43s%12d%12d%12s%12s\n", m_vecMsgPools[i]->GetName().String(), m_vecMsgPools[i]->GetAllocated(), m_vecMsgPools[i]->GetFree(), Q_pretifymem( (float)unEstimate, 2, true ), CFmtStr( "%f%%", flHitRate ).Access() );
|
||||
unTotalSize += unEstimate;
|
||||
}
|
||||
Msg( " -----------------------------------------------------------------------------------------\n" );
|
||||
Msg( " Total: %s\n", Q_pretifymem( (float)unTotalSize, 2, true ) );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor - no eMsg, so it's a receive constructor. Don't alloc
|
||||
// a header in this case
|
||||
//-----------------------------------------------------------------------------
|
||||
CProtoBufMsgBase::CProtoBufMsgBase()
|
||||
: m_pNetPacket( NULL )
|
||||
, m_eMsg( 0 )
|
||||
, m_pProtoBufHdr( NULL )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor - Has an eMsg, so it's a send constructor. Alloc a header
|
||||
//-----------------------------------------------------------------------------
|
||||
CProtoBufMsgBase::CProtoBufMsgBase( MsgType_t eMsg )
|
||||
: m_pNetPacket( NULL )
|
||||
, m_eMsg( eMsg )
|
||||
, m_pProtoBufHdr( NULL )
|
||||
{
|
||||
m_pProtoBufHdr = GProtoBufMsgMemoryPoolMgr()->AllocProtoBufHdr();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CProtoBufMsgBase::~CProtoBufMsgBase()
|
||||
{
|
||||
// If we have a net packet then it allocated the header, so we don't free it ourselves
|
||||
// If we don't have a net packet then we still may have not allocated a header ( default constructor )
|
||||
if ( m_pNetPacket )
|
||||
{
|
||||
m_pNetPacket->Release();
|
||||
m_pNetPacket = NULL;
|
||||
}
|
||||
else if ( m_pProtoBufHdr )
|
||||
{
|
||||
GProtoBufMsgMemoryPoolMgr()->FreeProtoBufHdr( m_pProtoBufHdr );
|
||||
}
|
||||
|
||||
m_pProtoBufHdr = NULL;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Inits a message from a received net packet
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CProtoBufMsgBase::InitFromPacket( IMsgNetPacket * pNetPacket )
|
||||
{
|
||||
VPROF_BUDGET( "CProtoBufMsg::InitFromPacket( IMsgNetPacket )", VPROF_BUDGETGROUP_OTHER_NETWORKING );
|
||||
|
||||
AssertMsg( NULL == m_pProtoBufHdr, "Encountered a packet being initialized that has outstanding memory. This will leak memory, and is often caused by receiving messages using the wrong constructor which preallocates or not clearing the message between uses" );
|
||||
Assert( k_EMsgFormatTypeProtocolBuffer == pNetPacket->GetEMsgFormatType() );
|
||||
|
||||
m_pNetPacket = static_cast<CProtoBufNetPacket *>( pNetPacket );
|
||||
m_pNetPacket->AddRef();
|
||||
ProtoBufMsgHeader_t &refFixedHdr = m_pNetPacket->GetFixedHeader();
|
||||
|
||||
m_eMsg = refFixedHdr.m_EMsgFlagged;
|
||||
m_pProtoBufHdr = m_pNetPacket->GetProtoHeader();
|
||||
|
||||
const uint8* pubData;
|
||||
uint32 cubData;
|
||||
if( !m_pNetPacket->GetMsgBody( pubData, cubData ) )
|
||||
return false;
|
||||
|
||||
::google::protobuf::Message *pBody = GetGenericBody();
|
||||
return pBody->ParseFromArray( pubData, (int)cubData );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Sends a message using the given handler
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CProtoBufMsgBase::BAsyncSend( IProtoBufSendHandler & sender ) const
|
||||
{
|
||||
return BAsyncSendProto( sender, GetEMsg(), *m_pProtoBufHdr, *GetGenericBody() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Sends a protobuf object out as a message
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CProtoBufMsgBase::BAsyncSendProto( IProtoBufSendHandler& sender, MsgType_t eMsgType, const CMsgProtoBufHeader& hdr, const ::google::protobuf::Message& proto )
|
||||
{
|
||||
VPROF_BUDGET( "CProtoBufMsg::BAsyncSendProto", VPROF_BUDGETGROUP_OTHER_NETWORKING );
|
||||
// Compute message sizes and serialize out to buffer. Need to work on a more efficient
|
||||
// way to write these to the net without so much copying.
|
||||
uint32 cubBodySize = proto.ByteSize();
|
||||
|
||||
uint32 cubTotalSize = 0;
|
||||
uint8* pMsgMem = AllocateMessageMemory( eMsgType, hdr, cubBodySize, &cubTotalSize );
|
||||
proto.SerializeWithCachedSizesToArray( pMsgMem + cubTotalSize - cubBodySize );
|
||||
|
||||
bool bSuccess = sender.BAsyncSend( eMsgType, pMsgMem, cubTotalSize );
|
||||
FreeMessageMemory( pMsgMem );
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Sends a message using the given handler
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CProtoBufMsgBase::BAsyncSendWithPreSerializedBody( IProtoBufSendHandler & pSender, const byte *pubBody, uint32 cubBody ) const
|
||||
{
|
||||
return BAsyncSendWithPreSerializedBody( pSender, GetEMsg(), *m_pProtoBufHdr, pubBody, cubBody );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//free standing version to send a protobuff given a header and pre-serialized body. Primarily used for efficient message routing
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CProtoBufMsgBase::BAsyncSendWithPreSerializedBody( IProtoBufSendHandler& sender, MsgType_t eMsgType, const CMsgProtoBufHeader& hdr, const byte* pubBody, uint32 cubBody )
|
||||
{
|
||||
VPROF_BUDGET( "CProtoBufMsg::BAsyncSendWithPreSerializedBody", VPROF_BUDGETGROUP_OTHER_NETWORKING );
|
||||
// Compute message sizes and serialize out to buffer. Need to work on a more efficient
|
||||
// way to write these to the net without so much copying.
|
||||
uint32 cubTotalSize = 0;
|
||||
uint8* pMsgMem = AllocateMessageMemory( eMsgType, hdr, cubBody, &cubTotalSize );
|
||||
|
||||
V_memcpy( pMsgMem + cubTotalSize - cubBody, pubBody, cubBody );
|
||||
|
||||
bool bSuccess = sender.BAsyncSend( eMsgType, pMsgMem, cubTotalSize );
|
||||
|
||||
FreeMessageMemory( pMsgMem );
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//utility function that handles allocating a memory pool big enough for the provided header and specified body
|
||||
//size and writing the header into the pool. This will return a pointer to the memory, as well as the header size.
|
||||
//-----------------------------------------------------------------------------
|
||||
uint8* CProtoBufMsgBase::AllocateMessageMemory( MsgType_t eMsgType, const CMsgProtoBufHeader& hdr, uint32 cubBodySize, uint32* pCubTotalSizeOut )
|
||||
{
|
||||
uint32 cubExtHdrSize = hdr.ByteSize();
|
||||
uint32 cubTotalSize = sizeof( ProtoBufMsgHeader_t ) + cubExtHdrSize + cubBodySize;
|
||||
ProtoBufMsgHeader_t fixedHdr( eMsgType, cubExtHdrSize );
|
||||
|
||||
uint8 *pubMsgBytes = (uint8*)g_MemPoolMsg.Alloc( cubTotalSize );
|
||||
|
||||
// Copy over basic fixed header
|
||||
// We place bytes on the network in little endian to optimize for those platforms, even though network byte order is really big endian.
|
||||
#if defined(VALVE_BIG_ENDIAN)
|
||||
ProtoBufMsgHeader_t hdrByteSwapped = fixedHdr;
|
||||
hdrByteSwapped.m_EMsgFlagged = DWordSwap( hdrByteSwapped.m_EMsgFlagged );
|
||||
hdrByteSwapped.m_cubProtoBufExtHdr = DWordSwap( hdrByteSwapped.m_cubProtoBufExtHdr );
|
||||
Q_memcpy( pubMsgBytes, &hdrByteSwapped, sizeof( ProtoBufMsgHeader_t ) );
|
||||
#else
|
||||
Q_memcpy( pubMsgBytes, &fixedHdr, sizeof( ProtoBufMsgHeader_t ) );
|
||||
#endif
|
||||
|
||||
// Serialize extended pb header, we guarantee we didn't modify the message since the last call to ByteSize above, so use the cached sizes serialize
|
||||
uint8 *pEnd = hdr.SerializeWithCachedSizesToArray( pubMsgBytes + sizeof( ProtoBufMsgHeader_t ) );
|
||||
NOTE_UNUSED( pEnd );
|
||||
Assert( pEnd == pubMsgBytes + sizeof( ProtoBufMsgHeader_t ) + cubExtHdrSize );
|
||||
|
||||
if( pCubTotalSizeOut )
|
||||
*pCubTotalSizeOut = cubTotalSize;
|
||||
return pubMsgBytes;
|
||||
}
|
||||
|
||||
void CProtoBufMsgBase::FreeMessageMemory( uint8* pMemory )
|
||||
{
|
||||
g_MemPoolMsg.Free( pMemory );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
//====== Copyright (c), Valve Corporation, All rights reserved. =======
|
||||
//
|
||||
// Purpose: Holds the CNetPacket class
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetPacket::CNetPacket()
|
||||
{
|
||||
m_cRef = 0;
|
||||
m_pubData = NULL;
|
||||
m_cubData = 0;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor, validates refcount
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetPacket::~CNetPacket()
|
||||
{
|
||||
Assert( m_cRef == 0 );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Inits all members
|
||||
// Input : hConnection - connection we're from
|
||||
// pubData - message data
|
||||
// cubData - message size
|
||||
// *pubNetworkBuffer - network buffer that contains the message
|
||||
// assumes control of this memory
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetPacket::Init( uint32 cubData, const void* pCopyData )
|
||||
{
|
||||
Assert( cubData );
|
||||
|
||||
m_pubData = (uint8 *)g_MemPoolMsg.Alloc( cubData );
|
||||
m_cubData = cubData;
|
||||
|
||||
if( pCopyData )
|
||||
{
|
||||
Q_memcpy( m_pubData, pCopyData, cubData );
|
||||
}
|
||||
|
||||
AddRef();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: adds to refcount
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetPacket::AddRef()
|
||||
{
|
||||
++m_cRef;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetPacket::InitAdoptBuffer( uint32 cubData, uint8* pubData )
|
||||
{
|
||||
Assert( !m_pubData );
|
||||
|
||||
m_pubData = pubData;
|
||||
m_cubData = cubData;
|
||||
|
||||
AddRef();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetPacket::OrphanBuffer()
|
||||
{
|
||||
m_pubData = NULL;
|
||||
m_cubData = 0;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: decrements refcount
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetPacket::Release()
|
||||
{
|
||||
Assert( m_cRef > 0 );
|
||||
if ( --m_cRef == 0 )
|
||||
{
|
||||
// delete the network buffer we're associated with, if we have one
|
||||
if ( m_pubData )
|
||||
{
|
||||
g_MemPoolMsg.Free( m_pubData );
|
||||
}
|
||||
// delete ourselves
|
||||
g_cNetPacket--;
|
||||
CNetPacketPool::sm_MemPoolNetPacket.Free( this );
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace GCSDK
|
||||
@@ -0,0 +1,37 @@
|
||||
//====== Copyright (c), Valve Corporation, All rights reserved. =======
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "netpacketpool.h"
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
CClassMemoryPool<CNetPacket> CNetPacketPool::sm_MemPoolNetPacket( k_cInitialNetworkBuffers, UTLMEMORYPOOL_GROW_FAST );
|
||||
|
||||
static const CThreadSafeMultiMemoryPool::MemPoolConfig_t s_MemPoolConfigAllocSize[] = {
|
||||
{ 32, 32 },
|
||||
{ 64, 32 },
|
||||
{ 96, 32 },
|
||||
{ 128, 32 },
|
||||
{ 256, 32 },
|
||||
{ k_cubMsgSizeSmall, 32 },
|
||||
{ 1312 /* sizeof( UDPRecvBuffer_t ) + some rounding */, 32 },
|
||||
{ 2048, 4 },
|
||||
{ 4096, 4 },
|
||||
{ 16384, 4 },
|
||||
{ 64*1024, 4 }
|
||||
};
|
||||
CThreadSafeMultiMemoryPool g_MemPoolMsg( s_MemPoolConfigAllocSize, Q_ARRAYSIZE(s_MemPoolConfigAllocSize), UTLMEMORYPOOL_GROW_FAST );
|
||||
|
||||
// hacky global
|
||||
int g_cNetPacket = 0;
|
||||
|
||||
} // namespace GCSDK
|
||||
@@ -0,0 +1,692 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Shared object based on a CBaseRecord subclass
|
||||
//
|
||||
//=============================================================================
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "gcsdk_gcmessages.pb.h"
|
||||
|
||||
#include "google/protobuf/descriptor.h"
|
||||
#include "google/protobuf/reflection_ops.h"
|
||||
#include "google/protobuf/descriptor.pb.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
using ::google::protobuf::FieldDescriptor;
|
||||
using ::google::protobuf::Descriptor;
|
||||
using ::google::protobuf::Message;
|
||||
using ::google::protobuf::Reflection;
|
||||
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: returns true if the field has the key_field option
|
||||
//----------------------------------------------------------------------------
|
||||
inline bool IsKeyField( const FieldDescriptor *pFieldDescriptor )
|
||||
{
|
||||
return pFieldDescriptor->options().GetExtension( key_field );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Copies a field from one protobuf message to another
|
||||
//----------------------------------------------------------------------------
|
||||
void CopyProtoBufField( ::google::protobuf::Message & msgDest, const ::google::protobuf::Message & msgSource, const ::google::protobuf::FieldDescriptor *pFieldDest, const ::google::protobuf::FieldDescriptor *pFieldSource )
|
||||
{
|
||||
Assert( pFieldDest->cpp_type() == pFieldSource->cpp_type() );
|
||||
Assert( pFieldDest->is_repeated() == pFieldSource->is_repeated() );
|
||||
if( pFieldSource->cpp_type() != pFieldDest->cpp_type() || pFieldSource->is_repeated() != pFieldDest->is_repeated() )
|
||||
return;
|
||||
|
||||
if ( pFieldDest->is_repeated() )
|
||||
{
|
||||
int nFieldSize = msgSource.GetReflection()->FieldSize( msgSource, pFieldSource );
|
||||
switch( pFieldDest->cpp_type() )
|
||||
{
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_INT32:
|
||||
{
|
||||
for ( int i = 0; i < nFieldSize; i++ )
|
||||
{
|
||||
if ( msgDest.GetReflection()->FieldSize( msgDest, pFieldDest ) <= i )
|
||||
{
|
||||
msgDest.GetReflection()->AddInt32( &msgDest, pFieldDest, msgSource.GetReflection()->GetRepeatedInt32( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
msgDest.GetReflection()->SetRepeatedInt32( &msgDest, pFieldDest, i, msgSource.GetReflection()->GetRepeatedInt32( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_INT64:
|
||||
{
|
||||
for ( int i = 0; i < nFieldSize; i++ )
|
||||
{
|
||||
if ( msgDest.GetReflection()->FieldSize( msgDest, pFieldDest ) <= i )
|
||||
{
|
||||
msgDest.GetReflection()->AddInt64( &msgDest, pFieldDest, msgSource.GetReflection()->GetRepeatedInt64( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
msgDest.GetReflection()->SetRepeatedInt64( &msgDest, pFieldDest, i, msgSource.GetReflection()->GetRepeatedInt64( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
|
||||
{
|
||||
for ( int i = 0; i < nFieldSize; i++ )
|
||||
{
|
||||
if ( msgDest.GetReflection()->FieldSize( msgDest, pFieldDest ) <= i )
|
||||
{
|
||||
msgDest.GetReflection()->AddUInt32( &msgDest, pFieldDest, msgSource.GetReflection()->GetRepeatedUInt32( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
msgDest.GetReflection()->SetRepeatedUInt32( &msgDest, pFieldDest, i, msgSource.GetReflection()->GetRepeatedUInt32( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
|
||||
{
|
||||
for ( int i = 0; i < nFieldSize; i++ )
|
||||
{
|
||||
if ( msgDest.GetReflection()->FieldSize( msgDest, pFieldDest ) <= i )
|
||||
{
|
||||
msgDest.GetReflection()->AddUInt64( &msgDest, pFieldDest, msgSource.GetReflection()->GetRepeatedUInt64( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
msgDest.GetReflection()->SetRepeatedUInt64( &msgDest, pFieldDest, i, msgSource.GetReflection()->GetRepeatedUInt64( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
|
||||
{
|
||||
for ( int i = 0; i < nFieldSize; i++ )
|
||||
{
|
||||
if ( msgDest.GetReflection()->FieldSize( msgDest, pFieldDest ) <= i )
|
||||
{
|
||||
msgDest.GetReflection()->AddDouble( &msgDest, pFieldDest, msgSource.GetReflection()->GetRepeatedDouble( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
msgDest.GetReflection()->SetRepeatedDouble( &msgDest, pFieldDest, i, msgSource.GetReflection()->GetRepeatedDouble( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
|
||||
{
|
||||
for ( int i = 0; i < nFieldSize; i++ )
|
||||
{
|
||||
if ( msgDest.GetReflection()->FieldSize( msgDest, pFieldDest ) <= i )
|
||||
{
|
||||
msgDest.GetReflection()->AddFloat( &msgDest, pFieldDest, msgSource.GetReflection()->GetRepeatedFloat( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
msgDest.GetReflection()->SetRepeatedFloat( &msgDest, pFieldDest, i, msgSource.GetReflection()->GetRepeatedFloat( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
|
||||
{
|
||||
for ( int i = 0; i < nFieldSize; i++ )
|
||||
{
|
||||
if ( msgDest.GetReflection()->FieldSize( msgDest, pFieldDest ) <= i )
|
||||
{
|
||||
msgDest.GetReflection()->AddBool( &msgDest, pFieldDest, msgSource.GetReflection()->GetRepeatedBool( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
msgDest.GetReflection()->SetRepeatedBool( &msgDest, pFieldDest, i, msgSource.GetReflection()->GetRepeatedBool( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
|
||||
{
|
||||
for ( int i = 0; i < nFieldSize; i++ )
|
||||
{
|
||||
if ( msgDest.GetReflection()->FieldSize( msgDest, pFieldDest ) <= i )
|
||||
{
|
||||
msgDest.GetReflection()->AddEnum( &msgDest, pFieldDest, msgSource.GetReflection()->GetRepeatedEnum( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
msgDest.GetReflection()->SetRepeatedEnum( &msgDest, pFieldDest, i, msgSource.GetReflection()->GetRepeatedEnum( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_STRING:
|
||||
{
|
||||
for ( int i = 0; i < nFieldSize; i++ )
|
||||
{
|
||||
if ( msgDest.GetReflection()->FieldSize( msgDest, pFieldDest ) <= i )
|
||||
{
|
||||
msgDest.GetReflection()->AddString( &msgDest, pFieldDest, msgSource.GetReflection()->GetRepeatedString( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
msgDest.GetReflection()->SetRepeatedString( &msgDest, pFieldDest, i, msgSource.GetReflection()->GetRepeatedString( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
|
||||
{
|
||||
for ( int i = 0; i < nFieldSize; i++ )
|
||||
{
|
||||
if ( msgDest.GetReflection()->FieldSize( msgDest, pFieldDest ) <= i )
|
||||
{
|
||||
msgDest.GetReflection()->AddMessage( &msgDest, pFieldDest )->CopyFrom( msgSource.GetReflection()->GetRepeatedMessage( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
msgDest.GetReflection()->MutableRepeatedMessage( &msgDest, pFieldDest, i )->CopyFrom( msgSource.GetReflection()->GetRepeatedMessage( msgSource, pFieldSource, i ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch( pFieldDest->cpp_type() )
|
||||
{
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_INT32:
|
||||
{
|
||||
msgDest.GetReflection()->SetInt32( &msgDest, pFieldDest, msgSource.GetReflection()->GetInt32( msgSource, pFieldSource ) );
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_INT64:
|
||||
{
|
||||
msgDest.GetReflection()->SetInt64( &msgDest, pFieldDest, msgSource.GetReflection()->GetInt64( msgSource, pFieldSource ) );
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
|
||||
{
|
||||
msgDest.GetReflection()->SetUInt32( &msgDest, pFieldDest, msgSource.GetReflection()->GetUInt32( msgSource, pFieldSource ) );
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
|
||||
{
|
||||
msgDest.GetReflection()->SetUInt64( &msgDest, pFieldDest, msgSource.GetReflection()->GetUInt64( msgSource, pFieldSource ) );
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
|
||||
{
|
||||
msgDest.GetReflection()->SetDouble( &msgDest, pFieldDest, msgSource.GetReflection()->GetDouble( msgSource, pFieldSource ) );
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
|
||||
{
|
||||
msgDest.GetReflection()->SetFloat( &msgDest, pFieldDest, msgSource.GetReflection()->GetFloat( msgSource, pFieldSource ) );
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
|
||||
{
|
||||
msgDest.GetReflection()->SetBool( &msgDest, pFieldDest, msgSource.GetReflection()->GetBool( msgSource, pFieldSource ) );
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
|
||||
{
|
||||
msgDest.GetReflection()->SetEnum( &msgDest, pFieldDest, msgSource.GetReflection()->GetEnum( msgSource, pFieldSource ) );
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_STRING:
|
||||
{
|
||||
msgDest.GetReflection()->SetString( &msgDest, pFieldDest, msgSource.GetReflection()->GetString( msgSource, pFieldSource ) );
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
|
||||
{
|
||||
msgDest.GetReflection()->MutableMessage( &msgDest, pFieldDest )->CopyFrom( msgSource.GetReflection()->GetMessage( msgSource, pFieldSource ) );
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: compares fields in two protobuf objects
|
||||
//----------------------------------------------------------------------------
|
||||
bool IsProtoBufFieldLess( const ::google::protobuf::Message & msgLHS, const ::google::protobuf::Message & msgRHS, const ::google::protobuf::FieldDescriptor *pFieldLHS, const ::google::protobuf::FieldDescriptor *pFieldRHS )
|
||||
{
|
||||
Assert( pFieldLHS->cpp_type() == pFieldRHS->cpp_type() );
|
||||
if( pFieldLHS->cpp_type() != pFieldRHS->cpp_type() )
|
||||
return false;
|
||||
|
||||
switch( pFieldLHS->cpp_type() )
|
||||
{
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_INT32:
|
||||
{
|
||||
return msgLHS.GetReflection()->GetInt32( msgLHS, pFieldLHS ) < msgRHS.GetReflection()->GetInt32( msgRHS, pFieldRHS );
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_INT64:
|
||||
{
|
||||
return msgLHS.GetReflection()->GetInt64( msgLHS, pFieldLHS ) < msgRHS.GetReflection()->GetInt64( msgRHS, pFieldRHS );
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
|
||||
{
|
||||
return msgLHS.GetReflection()->GetUInt32( msgLHS, pFieldLHS ) < msgRHS.GetReflection()->GetUInt32( msgRHS, pFieldRHS );
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
|
||||
{
|
||||
return msgLHS.GetReflection()->GetUInt64( msgLHS, pFieldLHS ) < msgRHS.GetReflection()->GetUInt64( msgRHS, pFieldRHS );
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
|
||||
{
|
||||
return msgLHS.GetReflection()->GetDouble( msgLHS, pFieldLHS ) < msgRHS.GetReflection()->GetDouble( msgRHS, pFieldRHS );
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
|
||||
{
|
||||
return msgLHS.GetReflection()->GetFloat( msgLHS, pFieldLHS ) < msgRHS.GetReflection()->GetFloat( msgRHS, pFieldRHS );
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
|
||||
{
|
||||
return msgLHS.GetReflection()->GetBool( msgLHS, pFieldLHS ) < msgRHS.GetReflection()->GetBool( msgRHS, pFieldRHS );
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
|
||||
{
|
||||
return msgLHS.GetReflection()->GetEnum( msgLHS, pFieldLHS )->number() < msgRHS.GetReflection()->GetEnum( msgRHS, pFieldRHS )->number();
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_STRING:
|
||||
{
|
||||
return Q_stricmp( msgLHS.GetReflection()->GetString( msgLHS, pFieldLHS ).c_str(), msgRHS.GetReflection()->GetString( msgRHS, pFieldRHS ).c_str() ) > 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case ::google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// unknown field types return false
|
||||
return false;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Returns true if the key fields in LHS are less than the key fields
|
||||
// in RHS
|
||||
//----------------------------------------------------------------------------
|
||||
bool CProtoBufSharedObjectBase::BIsKeyLess( const CSharedObject & soRHS ) const
|
||||
{
|
||||
const ::google::protobuf::Message & msgLHS = *GetPObject();
|
||||
const ::google::protobuf::Message & msgRHS = *((const CProtoBufSharedObjectBase &)soRHS).GetPObject();
|
||||
|
||||
const ::google::protobuf::Descriptor *pDescriptor = msgLHS.GetDescriptor();
|
||||
|
||||
for( int nField = 0; nField < pDescriptor->field_count(); nField++ )
|
||||
{
|
||||
const ::google::protobuf::FieldDescriptor *pFieldDescriptor = pDescriptor->field( nField );
|
||||
if( !IsKeyField( pFieldDescriptor ) )
|
||||
continue;
|
||||
|
||||
if( IsProtoBufFieldLess( msgLHS, msgRHS, pFieldDescriptor, pFieldDescriptor ) )
|
||||
return true;
|
||||
|
||||
// if the fields are less when reversed it means we know the keys aren't equal and don't
|
||||
// need to keep looping
|
||||
if( IsProtoBufFieldLess( msgRHS, msgLHS, pFieldDescriptor, pFieldDescriptor ) )
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void CProtoBufSharedObjectBase::RecursiveAddProtoBufToKV( KeyValues *pKVDest, const ::google::protobuf::Message & msg )
|
||||
{
|
||||
using ::google::protobuf::FieldDescriptor;
|
||||
const ::google::protobuf::Descriptor *pDescriptor = msg.GetDescriptor();
|
||||
const ::google::protobuf::Reflection *pReflection = msg.GetReflection();
|
||||
|
||||
for ( int iField = 0; iField < pDescriptor->field_count(); iField++ )
|
||||
{
|
||||
const ::google::protobuf::FieldDescriptor *pField = pDescriptor->field( iField );
|
||||
const char *pFieldName = pField->name().c_str();
|
||||
|
||||
if ( pField->is_repeated() )
|
||||
{
|
||||
KeyValues *pKVContainer = pKVDest->FindKey( pFieldName, true );
|
||||
for ( int iRepeated = 0; iRepeated < pReflection->FieldSize( msg, pField ); iRepeated++ )
|
||||
{
|
||||
KeyValues *pKVNode = pKVContainer->CreateNewKey();
|
||||
switch ( pField->cpp_type() )
|
||||
{
|
||||
case FieldDescriptor::CPPTYPE_INT32: pKVNode->SetInt( NULL, pReflection->GetRepeatedInt32( msg, pField, iRepeated ) ); break;
|
||||
case FieldDescriptor::CPPTYPE_INT64: pKVNode->SetUint64( NULL, (uint64)pReflection->GetRepeatedInt64( msg, pField, iRepeated ) ); break;
|
||||
case FieldDescriptor::CPPTYPE_UINT32: pKVNode->SetInt( NULL, (int32)pReflection->GetRepeatedUInt32( msg, pField, iRepeated ) ); break;
|
||||
case FieldDescriptor::CPPTYPE_UINT64: pKVNode->SetUint64( NULL, pReflection->GetRepeatedUInt64( msg, pField, iRepeated ) ); break;
|
||||
case FieldDescriptor::CPPTYPE_DOUBLE: pKVNode->SetFloat( NULL, (float)pReflection->GetRepeatedDouble( msg, pField, iRepeated ) ); break;
|
||||
case FieldDescriptor::CPPTYPE_FLOAT: pKVNode->SetFloat( NULL, pReflection->GetRepeatedFloat( msg, pField, iRepeated ) ); break;
|
||||
case FieldDescriptor::CPPTYPE_BOOL: pKVNode->SetBool( NULL, pReflection->GetRepeatedBool( msg, pField, iRepeated ) ); break;
|
||||
case FieldDescriptor::CPPTYPE_ENUM: pKVNode->SetInt( NULL, pReflection->GetRepeatedEnum( msg, pField, iRepeated )->number() ); break;
|
||||
case FieldDescriptor::CPPTYPE_STRING: pKVNode->SetString( NULL, pReflection->GetRepeatedString( msg, pField, iRepeated ).c_str() ); break;
|
||||
case FieldDescriptor::CPPTYPE_MESSAGE:
|
||||
{
|
||||
const ::google::protobuf::Message &subMsg = pReflection->GetRepeatedMessage( msg, pField, iRepeated );
|
||||
RecursiveAddProtoBufToKV( pKVNode, subMsg );
|
||||
break;
|
||||
}
|
||||
default:
|
||||
AssertMsg1( false, "Unknown cpp_type %d", pField->cpp_type() );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch ( pField->cpp_type() )
|
||||
{
|
||||
case FieldDescriptor::CPPTYPE_INT32: pKVDest->SetInt( pFieldName, pReflection->GetInt32( msg, pField ) ); break;
|
||||
case FieldDescriptor::CPPTYPE_INT64: pKVDest->SetUint64( pFieldName, (uint64)pReflection->GetInt64( msg, pField ) ); break;
|
||||
case FieldDescriptor::CPPTYPE_UINT32: pKVDest->SetInt( pFieldName, (int32)pReflection->GetUInt32( msg, pField ) ); break;
|
||||
case FieldDescriptor::CPPTYPE_UINT64: pKVDest->SetUint64( pFieldName, pReflection->GetUInt64( msg, pField ) ); break;
|
||||
case FieldDescriptor::CPPTYPE_DOUBLE: pKVDest->SetFloat( pFieldName, (float)pReflection->GetDouble( msg, pField ) ); break;
|
||||
case FieldDescriptor::CPPTYPE_FLOAT: pKVDest->SetFloat( pFieldName, pReflection->GetFloat( msg, pField ) ); break;
|
||||
case FieldDescriptor::CPPTYPE_BOOL: pKVDest->SetBool( pFieldName, pReflection->GetBool( msg, pField ) ); break;
|
||||
case FieldDescriptor::CPPTYPE_ENUM: pKVDest->SetInt( pFieldName, pReflection->GetEnum( msg, pField )->number() );break;
|
||||
case FieldDescriptor::CPPTYPE_STRING: pKVDest->SetString( pFieldName, pReflection->GetString( msg, pField ).c_str() );break;
|
||||
case FieldDescriptor::CPPTYPE_MESSAGE:
|
||||
{
|
||||
KeyValues *pKVSub = pKVDest->FindKey( pFieldName, true );
|
||||
const ::google::protobuf::Message &subMsg = pReflection->GetMessage( msg, pField );
|
||||
RecursiveAddProtoBufToKV( pKVSub, subMsg );
|
||||
break;
|
||||
}
|
||||
default:
|
||||
AssertMsg1( false, "Unknown cpp_type %d", pField->cpp_type() );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
KeyValues *CProtoBufSharedObjectBase::CreateKVFromProtoBuf( const ::google::protobuf::Message & msg )
|
||||
{
|
||||
KeyValues *pKVDest = new KeyValues( msg.GetDescriptor()->name().c_str() );
|
||||
RecursiveAddProtoBufToKV( pKVDest, msg );
|
||||
return pKVDest;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Dumps a debug string for the object
|
||||
//----------------------------------------------------------------------------
|
||||
void CProtoBufSharedObjectBase::Dump( const ::google::protobuf::Message & msg )
|
||||
{
|
||||
// print line by line to get round our limited emitinfo buffer
|
||||
CUtlStringList lines;
|
||||
V_SplitString( msg.DebugString().c_str(), "\n", lines );
|
||||
for ( int i = 0; i < lines.Count(); i++ )
|
||||
{
|
||||
EmitInfo( SPEW_SHAREDOBJ, SPEW_ALWAYS, LOG_ALWAYS, "%s\n", lines[i] );
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Dumps a debug string for the object
|
||||
//----------------------------------------------------------------------------
|
||||
void CProtoBufSharedObjectBase::Dump() const
|
||||
{
|
||||
Dump( *GetPObject() );
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Parses the message bits for creating this object from the message.
|
||||
// This will be called on the client/gameserver when it first learns
|
||||
// about the item.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CProtoBufSharedObjectBase::BParseFromMessage( const CUtlBuffer & buffer )
|
||||
{
|
||||
::google::protobuf::Message & msg = *GetPObject();
|
||||
|
||||
return msg.ParseFromArray( buffer.Base(), buffer.TellMaxPut() );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Parses the message bits for creating this object from the message.
|
||||
// This will be called on the client/gameserver when it first learns
|
||||
// about the item.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CProtoBufSharedObjectBase::BParseFromMessage( const std::string &buffer )
|
||||
{
|
||||
::google::protobuf::Message & msg = *GetPObject();
|
||||
|
||||
return msg.ParseFromString( buffer );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Overrides all the fields in msgLocal that are present in the
|
||||
// network message
|
||||
//----------------------------------------------------------------------------
|
||||
bool CProtoBufSharedObjectBase::BUpdateFromNetwork( const CSharedObject & objUpdate )
|
||||
{
|
||||
::google::protobuf::Message & msg = *GetPObject();
|
||||
const CProtoBufSharedObjectBase & pbobjUpdate = (const CProtoBufSharedObjectBase &)objUpdate;
|
||||
|
||||
// merge the update onto the local message
|
||||
msg.CopyFrom( *pbobjUpdate.GetPObject() );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#ifdef GC
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Static help class that seralizes to a buffer
|
||||
//----------------------------------------------------------------------------
|
||||
bool CProtoBufSharedObjectBase::SerializeToBuffer( const ::google::protobuf::Message & msg, CUtlBuffer & bufOutput )
|
||||
{
|
||||
uint32 unSize = msg.ByteSize();
|
||||
bufOutput.Clear();
|
||||
bufOutput.EnsureCapacity( unSize );
|
||||
msg.SerializeWithCachedSizesToArray( (uint8*)bufOutput.Base() );
|
||||
bufOutput.SeekPut( CUtlBuffer::SEEK_HEAD, unSize );
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Adds the relevant message bits to create this object to the
|
||||
// message. This will be called whenever a subscriber is added.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CProtoBufSharedObjectBase::BAddToMessage( CUtlBuffer & bufOutput ) const
|
||||
{
|
||||
const ::google::protobuf::Message & msg = *GetPObject();
|
||||
SerializeToBuffer( msg, bufOutput );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Adds the relevant message bits to create this object to the
|
||||
// message. This will be called whenever a subscriber is added.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CProtoBufSharedObjectBase::BAddToMessage( std::string *pBuffer ) const
|
||||
{
|
||||
const ::google::protobuf::Message & msg = *GetPObject();
|
||||
|
||||
return msg.SerializeToString( pBuffer );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Parses the message bits for creating this object from the message.
|
||||
// This will be called on the client/gameserver when it first learns
|
||||
// about the item.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CProtoBufSharedObjectBase::BParseFromMemcached( CUtlBuffer & buffer )
|
||||
{
|
||||
::google::protobuf::Message & msg = *GetPObject();
|
||||
return msg.ParseFromArray( buffer.Base(), buffer.TellMaxPut() );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Adds the relevant message bits to create this object to the
|
||||
// message. This will be called whenever a subscriber is added.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CProtoBufSharedObjectBase::BAddToMemcached( CUtlBuffer & bufOutput ) const
|
||||
{
|
||||
const ::google::protobuf::Message & msg = *GetPObject();
|
||||
SerializeToBuffer( msg, bufOutput );
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Adds all the information required for this object on the client
|
||||
//----------------------------------------------------------------------------
|
||||
bool CProtoBufSharedObjectBase::BAppendToMessage( CUtlBuffer & bufOutput ) const
|
||||
{
|
||||
const ::google::protobuf::Message & msg = *GetPObject();
|
||||
|
||||
uint32 unSize = msg.ByteSize();
|
||||
bufOutput.EnsureCapacity( bufOutput.Size() + unSize );
|
||||
msg.SerializeWithCachedSizesToArray( (uint8*)bufOutput.Base() + bufOutput.TellPut() );
|
||||
bufOutput.SeekPut( CUtlBuffer::SEEK_HEAD, bufOutput.TellPut() + unSize );
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Adds all the information required for this object on the client
|
||||
//----------------------------------------------------------------------------
|
||||
bool CProtoBufSharedObjectBase::BAppendToMessage( std::string *pBuffer ) const
|
||||
{
|
||||
const ::google::protobuf::Message & msg = *GetPObject();
|
||||
|
||||
msg.AppendToString( pBuffer );
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
::google::protobuf::Message *CProtoBufSharedObjectBase::BuildDestroyToMessage( const ::google::protobuf::Message & msg )
|
||||
{
|
||||
const ::google::protobuf::Descriptor *pDescriptor = msg.GetDescriptor();
|
||||
::google::protobuf::Message *pMessageToSend = msg.New();
|
||||
|
||||
for( int nField = 0; nField < pDescriptor->field_count(); nField++ )
|
||||
{
|
||||
const ::google::protobuf::FieldDescriptor *pFieldDescriptor = pDescriptor->field( nField );
|
||||
if( !IsKeyField( pFieldDescriptor ) )
|
||||
continue;
|
||||
|
||||
CopyProtoBufField( *pMessageToSend, msg, pFieldDescriptor, pFieldDescriptor );
|
||||
}
|
||||
|
||||
return pMessageToSend;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Adds just the key fields to the message
|
||||
//----------------------------------------------------------------------------
|
||||
bool CProtoBufSharedObjectBase::BAddDestroyToMessage( CUtlBuffer & bufDestroy ) const
|
||||
{
|
||||
const ::google::protobuf::Message & msg = *GetPObject();
|
||||
|
||||
::google::protobuf::Message *pMessageToSend = BuildDestroyToMessage( msg );
|
||||
|
||||
SerializeToBuffer( *pMessageToSend, bufDestroy );
|
||||
delete pMessageToSend;
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Adds just the key fields to the message
|
||||
//----------------------------------------------------------------------------
|
||||
bool CProtoBufSharedObjectBase::BAddDestroyToMessage( std::string *pBuffer ) const
|
||||
{
|
||||
const ::google::protobuf::Message & msg = *GetPObject();
|
||||
|
||||
::google::protobuf::Message *pMessageToSend = BuildDestroyToMessage( msg );
|
||||
pMessageToSend->SerializeToString( pBuffer );
|
||||
delete pMessageToSend;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif //GC
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Copy the data from the specified schema shared object into this.
|
||||
// Both objects must be of the same type.
|
||||
//----------------------------------------------------------------------------
|
||||
void CProtoBufSharedObjectBase::Copy( const CSharedObject & soRHS )
|
||||
{
|
||||
Assert( GetTypeID() == soRHS.GetTypeID() );
|
||||
const CProtoBufSharedObjectBase & soRHSBase = (CProtoBufSharedObjectBase &)soRHS;
|
||||
GetPObject()->CopyFrom( *soRHSBase.GetPObject() );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Claims all memory for the object.
|
||||
//----------------------------------------------------------------------------
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
void CProtoBufSharedObjectBase::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
CSharedObject::Validate( validator, pchName );
|
||||
|
||||
// these are INSIDE the function instead of outside so the interface
|
||||
// doesn't change
|
||||
VALIDATE_SCOPE();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
} // namespace GCSDK
|
||||
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//=============================================================================//
|
||||
#include "stdafx.h"
|
||||
#include "scheduledfunction.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
|
||||
IGCScheduledFunction::~IGCScheduledFunction()
|
||||
{
|
||||
GScheduledFunctionMgr().Cancel( this );
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
CGlobalScheduledFunction::CGlobalScheduledFunction() :
|
||||
m_pfn( NULL )
|
||||
{}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
void CGlobalScheduledFunction::ScheduleMS( func_t pfn, uint32 nDelayMS )
|
||||
{
|
||||
m_pfn = pfn;
|
||||
GScheduledFunctionMgr().ScheduleMS( this, nDelayMS );
|
||||
}
|
||||
|
||||
void CGlobalScheduledFunction::ScheduleSecond( func_t pfn, uint32 nDelaySecond )
|
||||
{
|
||||
m_pfn = pfn;
|
||||
GScheduledFunctionMgr().ScheduleSecond( this, nDelaySecond );
|
||||
}
|
||||
|
||||
void CGlobalScheduledFunction::ScheduleMinute( func_t pfn, uint32 nDelayMinute )
|
||||
{
|
||||
m_pfn = pfn;
|
||||
GScheduledFunctionMgr().ScheduleMinute( this, nDelayMinute );
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
void CGlobalScheduledFunction::Cancel()
|
||||
{
|
||||
GScheduledFunctionMgr().Cancel( this );
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
void CGlobalScheduledFunction::OnEvent()
|
||||
{
|
||||
m_pfn();
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
CScheduledFunctionMgr::CScheduledFunctionMgr()
|
||||
{
|
||||
const uint32 knSecond = 1000000;
|
||||
//frame rate resolution - 30s at 20Hz
|
||||
m_Resolutions[ 0 ].Init( m_ScheduleList, 600, k_cMicroSecPerShellFrame );
|
||||
//second resolution - 15 minutes
|
||||
m_Resolutions[ 1 ].Init( m_ScheduleList, 15 * 60, knSecond );
|
||||
//minute resolution - 5 hours
|
||||
m_Resolutions[ 2 ].Init( m_ScheduleList, 5 * 60, 60 * knSecond );
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
void CScheduledFunctionMgr::InitStartingTime()
|
||||
{
|
||||
uint64 nCurrTime = CJobTime::LJobTimeCur();
|
||||
for( uint32 nCurrBucket = 0; nCurrBucket < ARRAYSIZE( m_Resolutions ); nCurrBucket++ )
|
||||
{
|
||||
m_Resolutions[ nCurrBucket ].m_nAbsLastScheduleBucket = m_Resolutions[ nCurrBucket ].GetAbsScheduleBucketIndex( nCurrTime );
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
void CScheduledFunctionMgr::InternalSchedule( uint32 nResolution, IGCScheduledFunction* pEvent, uint32 nMSDelay )
|
||||
{
|
||||
//if the event is already registered, deregister it, double registration would be a very bad thing
|
||||
if( pEvent->BIsScheduled() )
|
||||
{
|
||||
Cancel( pEvent );
|
||||
}
|
||||
|
||||
//determine which bucket this belongs in
|
||||
uint32 nAbsBucket = m_Resolutions[ nResolution ].GetAbsScheduleBucketIndex( CJobTime::LJobTimeCur() + ( uint64 )nMSDelay * 1000 );
|
||||
//so we can remove it, and deal with wrapping
|
||||
pEvent->m_nAbsScheduleBucket = nAbsBucket;
|
||||
|
||||
//add it to our list
|
||||
uint32 nInsertAfter = m_Resolutions[ nResolution ].m_pBuckets[ nAbsBucket % m_Resolutions[ nResolution ].m_nNumBuckets ];
|
||||
pEvent->m_nLLIndex = m_ScheduleList.InsertAfter( nInsertAfter, pEvent );
|
||||
}
|
||||
|
||||
void CScheduledFunctionMgr::ScheduleMS( IGCScheduledFunction* pEvent, uint32 nMSDelay )
|
||||
{
|
||||
InternalSchedule( 0, pEvent, nMSDelay );
|
||||
}
|
||||
|
||||
void CScheduledFunctionMgr::ScheduleSecond( IGCScheduledFunction* pEvent, uint32 nSDelay )
|
||||
{
|
||||
InternalSchedule( 1, pEvent, nSDelay * 1000 );
|
||||
}
|
||||
|
||||
void CScheduledFunctionMgr::ScheduleMinute( IGCScheduledFunction* pEvent, uint32 nMinuteDelay )
|
||||
{
|
||||
InternalSchedule( 2, pEvent, nMinuteDelay * 1000 * 60 );
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
void CScheduledFunctionMgr::Cancel( IGCScheduledFunction* pEvent )
|
||||
{
|
||||
//ignore it if not already registered
|
||||
if( pEvent->m_nAbsScheduleBucket == IGCScheduledFunction::knInvalidBucket )
|
||||
return;
|
||||
|
||||
if( m_ScheduleList.IsValidIndex( pEvent->m_nLLIndex ) )
|
||||
{
|
||||
m_ScheduleList.Remove( pEvent->m_nLLIndex );
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertMsg( false, "Warning: Ecountered a remove request for a scheduled event but was unable to find it in the bucket that it was supposed to be in" );
|
||||
}
|
||||
|
||||
pEvent->m_nAbsScheduleBucket = IGCScheduledFunction::knInvalidBucket;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
void CScheduledFunctionMgr::RunFunctions()
|
||||
{
|
||||
VPROF_BUDGET( "CGCBase::CallScheduledEvents", VPROF_BUDGETGROUP_STEAM );
|
||||
|
||||
uint64 nCurrTime = CJobTime::LJobTimeCur();
|
||||
for( uint32 nCurrBucket = 0; nCurrBucket < ARRAYSIZE( m_Resolutions ); nCurrBucket++ )
|
||||
{
|
||||
m_Resolutions[ nCurrBucket ].RunFunctions( m_ScheduleList, nCurrTime );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
CScheduledFunctionMgr& GScheduledFunctionMgr()
|
||||
{
|
||||
static CScheduledFunctionMgr s_Singleton;
|
||||
return s_Singleton;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
CScheduledFunctionMgr::CScheduleBucket::CScheduleBucket() :
|
||||
m_pBuckets( NULL ),
|
||||
m_nNumBuckets( 0 ),
|
||||
m_nMicroSPerBucket( 0 ),
|
||||
m_nAbsLastScheduleBucket( 0 )
|
||||
{}
|
||||
|
||||
CScheduledFunctionMgr::CScheduleBucket::~CScheduleBucket()
|
||||
{
|
||||
delete [] m_pBuckets;
|
||||
}
|
||||
|
||||
void CScheduledFunctionMgr::CScheduleBucket::Init( TScheduleList& MasterList, uint32 nNumBuckets, uint32 nMicroSPerBucket )
|
||||
{
|
||||
//init should never happen more than once
|
||||
AssertMsg( !m_pBuckets, "Error: Schedule buckets should never be initialized multiple times" );
|
||||
|
||||
m_pBuckets = new uint32[ nNumBuckets ];
|
||||
m_nNumBuckets = nNumBuckets;
|
||||
m_nMicroSPerBucket = nMicroSPerBucket;
|
||||
|
||||
for( uint32 nCurrBucket = 0; nCurrBucket < nNumBuckets; nCurrBucket++ )
|
||||
{
|
||||
m_pBuckets[ nCurrBucket ] = MasterList.AddToTail( NULL );
|
||||
}
|
||||
}
|
||||
|
||||
void CScheduledFunctionMgr::CScheduleBucket::RunFunctions( TScheduleList& MasterList, uint64 nMicroSTime )
|
||||
{
|
||||
//determine the absolute bucket index of our current frame that we want to run to
|
||||
uint32 nCurrFrameBucket = GetAbsScheduleBucketIndex( nMicroSTime );
|
||||
|
||||
//note that we include the starting bucket, but not the ending bucket. This addresses two issues: it avoids checking buckets multiple times, and since we may not have completely spent
|
||||
//all the time for the frame, events will not fire early
|
||||
for( uint32 nAbsBucket = m_nAbsLastScheduleBucket; nAbsBucket < nCurrFrameBucket; nAbsBucket++ )
|
||||
{
|
||||
//map this to a relative bucket
|
||||
uint32 nStartIndex = m_pBuckets[ nAbsBucket % m_nNumBuckets ];
|
||||
AssertMsg2( nStartIndex != MasterList.InvalidIndex(), "Error: Detected list corruption when accessing list bucket %d (%d micro s per bucket)", nAbsBucket % m_nNumBuckets, m_nMicroSPerBucket );
|
||||
for( uint32 nCurrEvent = MasterList.Next( nStartIndex ); nCurrEvent != MasterList.InvalidIndex(); )
|
||||
{
|
||||
//make sure to advance to the next item immediately since we'll be removing this element from our list
|
||||
uint32 nEventToRemove = nCurrEvent;
|
||||
IGCScheduledFunction* pEvent = MasterList[ nCurrEvent ];
|
||||
nCurrEvent = MasterList.Next( nCurrEvent );
|
||||
|
||||
//see if we have hit the end of our bucket
|
||||
if( !pEvent )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
//skip any event that happens in the future (can occur since we wrap the frame list over itself to map to an actual bucket)
|
||||
if( pEvent->m_nAbsScheduleBucket > nAbsBucket )
|
||||
{
|
||||
//just skip over this element
|
||||
continue;
|
||||
}
|
||||
|
||||
//sanity check that this bucket matches exactly. We'll process stale ones, but this warns us that we missed an event and cycled through the bucket, causing it be very delayed
|
||||
AssertMsg2( pEvent->m_nAbsScheduleBucket == nAbsBucket, "Warning: Encountered a scheduled event that was intended to be fired on bucket %d but wasn't until bucket %d", pEvent->m_nAbsScheduleBucket, nAbsBucket );
|
||||
|
||||
//mark the object has having been removed
|
||||
pEvent->m_nAbsScheduleBucket = IGCScheduledFunction::knInvalidBucket;
|
||||
pEvent->m_nLLIndex = MasterList.InvalidIndex();
|
||||
|
||||
//remove it from our list
|
||||
MasterList.Remove( nEventToRemove );
|
||||
|
||||
//call into the object
|
||||
pEvent->OnEvent();
|
||||
//NOTE: You cannot use the object any more, it may have destroyed itself!!!!
|
||||
pEvent = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
//and update the event range we need to process
|
||||
m_nAbsLastScheduleBucket = nCurrFrameBucket;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} //namespace GCSDK
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Shared object based on a CBaseRecord subclass
|
||||
//
|
||||
//=============================================================================
|
||||
#include "stdafx.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
#ifdef GC
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Returns true if the object needs to be saved to the database
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSchemaSharedObjectHelper::BYieldingAddToDatabase( CRecordBase *pRecordBase )
|
||||
{
|
||||
if( pRecordBase->GetPSchema()->GetRecordInfo()->BHasIdentity() )
|
||||
{
|
||||
CSQLAccess sqlAccess;
|
||||
return sqlAccess.BYieldingInsertWithIdentity( pRecordBase );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Returns true if the object needs to be saved to the database
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSchemaSharedObjectHelper::BYieldingAddInsertToTransaction( CSQLAccess & sqlAccess, CRecordBase *pRecordBase )
|
||||
{
|
||||
Assert( !pRecordBase->GetPSchema()->GetRecordInfo()->BHasIdentity() );
|
||||
return sqlAccess.BYieldingInsertRecord( pRecordBase );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Reads the object from the database via its primary key. Initialize
|
||||
// the primary key fields before you call this.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSchemaSharedObjectHelper::BYieldingReadFromDatabase( CRecordBase *pRecordBase )
|
||||
{
|
||||
CSQLAccess sqlAccess;
|
||||
CColumnSet csPK( pRecordBase->GetPSchema()->GetRecordInfo() );
|
||||
csPK.MakePrimaryKey();
|
||||
CColumnSet csReadFields( pRecordBase->GetPSchema()->GetRecordInfo() );
|
||||
csReadFields.MakeInverse( csPK );
|
||||
if( k_EResultOK != sqlAccess.YieldingReadRecordWithWhereColumns( pRecordBase, csReadFields, csPK ) )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Writes the non-PK fields on the object to the database
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSchemaSharedObjectHelper::BYieldingAddWriteToTransaction( CSQLAccess & sqlAccess, CRecordBase *pRecordBase, const CColumnSet & csDatabaseDirty )
|
||||
{
|
||||
CColumnSet csPK( pRecordBase->GetPSchema()->GetRecordInfo() );
|
||||
csPK.MakePrimaryKey();
|
||||
|
||||
if( !sqlAccess.BYieldingUpdateRecord( *pRecordBase, csPK, csDatabaseDirty ) )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Adds the primary key for this object to the network message
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSchemaSharedObjectHelper::BYieldingAddRemoveToTransaction( CSQLAccess & sqlAccess, CRecordBase *pRecordBase )
|
||||
{
|
||||
CColumnSet csPK( pRecordBase->GetPSchema()->GetRecordInfo() );
|
||||
csPK.MakePrimaryKey();
|
||||
|
||||
if( !sqlAccess.BYieldingDeleteRecords( *pRecordBase, csPK ) )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif // GC
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Dumps diagnostic information about the shared object
|
||||
//----------------------------------------------------------------------------
|
||||
void CSchemaSharedObjectHelper::Dump( const CRecordBase *pRecordBase )
|
||||
{
|
||||
CFmtStr1024 sOutput;
|
||||
const CRecordInfo *pRecordInfo = pRecordBase->GetPRecordInfo();
|
||||
for( uint32 unColumn = 0; unColumn < (uint32)pRecordInfo->GetNumColumns(); unColumn++ )
|
||||
{
|
||||
if( unColumn > 0 )
|
||||
{
|
||||
sOutput += ", ";
|
||||
}
|
||||
|
||||
sOutput += pRecordInfo->GetColumnInfo( unColumn ).GetName();
|
||||
sOutput += "=";
|
||||
|
||||
char pchFieldData[128];
|
||||
pRecordBase->RenderField( unColumn, sizeof(pchFieldData), pchFieldData );
|
||||
sOutput += pchFieldData;
|
||||
}
|
||||
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\t\t%s\n", sOutput.Access() );
|
||||
}
|
||||
|
||||
|
||||
//=============================================================================
|
||||
|
||||
|
||||
|
||||
#ifdef GC
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Returns true if the object needs to be saved to the database
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSchemaSharedObjectBase::BYieldingAddToDatabase()
|
||||
{
|
||||
if( GetPObject()->GetPSchema()->GetRecordInfo()->BHasIdentity() )
|
||||
{
|
||||
return CSchemaSharedObjectHelper::BYieldingAddToDatabase( GetPObject() );
|
||||
}
|
||||
else
|
||||
return CSharedObject::BYieldingAddToDatabase();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Returns true if the object needs to be saved to the database
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSchemaSharedObjectBase::BYieldingAddInsertToTransaction( CSQLAccess & sqlAccess )
|
||||
{
|
||||
return CSchemaSharedObjectHelper::BYieldingAddInsertToTransaction( sqlAccess, GetPObject() );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Reads the object from the database via its primary key. Initialize
|
||||
// the primary key fields before you call this.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSchemaSharedObjectBase::BYieldingReadFromDatabase()
|
||||
{
|
||||
return CSchemaSharedObjectHelper::BYieldingReadFromDatabase( GetPObject() );
|
||||
}
|
||||
|
||||
CColumnSet CSchemaSharedObjectBase::GetDatabaseDirtyColumnSet( const CUtlVector< int > &fields ) const
|
||||
{
|
||||
CColumnSet cs( GetPObject()->GetPRecordInfo() );
|
||||
FOR_EACH_VEC( fields, i )
|
||||
{
|
||||
cs.BAddColumn( fields[i] );
|
||||
}
|
||||
|
||||
return cs;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Writes the non-PK fields on the object to the database
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSchemaSharedObjectBase::BYieldingAddWriteToTransaction( CSQLAccess & sqlAccess, const CUtlVector< int > &fields )
|
||||
{
|
||||
CColumnSet csDatabaseDirty = GetDatabaseDirtyColumnSet(fields);
|
||||
return CSchemaSharedObjectHelper::BYieldingAddWriteToTransaction( sqlAccess, GetPObject(), csDatabaseDirty );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Adds the primary key for this object to the network message
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSchemaSharedObjectBase::BYieldingAddRemoveToTransaction( CSQLAccess & sqlAccess )
|
||||
{
|
||||
if ( CSchemaSharedObjectHelper::BYieldingAddRemoveToTransaction( sqlAccess, GetPObject() ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
#endif // GC
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Returns true if this is less than than the object in soRHS. This
|
||||
// comparison is deterministic, but it may not be pleasing to a user
|
||||
// since it is just going to compare raw memory. If you need a sort
|
||||
// that is user-visible you will need to do it at a higher level that
|
||||
// actually knows what the data in these objects means.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSchemaSharedObjectBase::BIsKeyLess( const CSharedObject & soRHS ) const
|
||||
{
|
||||
Assert( GetTypeID() == soRHS.GetTypeID() );
|
||||
const CSchemaSharedObjectBase & soSchemaRHS = (const CSchemaSharedObjectBase &)soRHS;
|
||||
|
||||
CColumnSet csPK( GetPObject()->GetPSchema()->GetRecordInfo() );
|
||||
csPK.MakePrimaryKey();
|
||||
|
||||
FOR_EACH_COLUMN_IN_SET( csPK, unColumnIndex )
|
||||
{
|
||||
uint8 *pubMyData, *pubTheirData;
|
||||
uint32 cubMyData, cubTheirData;
|
||||
int nColumn = csPK.GetColumn( unColumnIndex );
|
||||
DbgVerify( GetPObject()->BGetField( nColumn, &pubMyData, &cubMyData ) );
|
||||
DbgVerify( soSchemaRHS.GetPObject()->BGetField( nColumn, &pubTheirData, &cubTheirData ) );
|
||||
|
||||
int nCmp = Q_memcmp( pubMyData, pubTheirData, MIN( cubMyData, cubTheirData ) );
|
||||
|
||||
// longer chunks of memory are greater
|
||||
if( nCmp == 0 )
|
||||
{
|
||||
nCmp = cubTheirData > cubMyData;
|
||||
}
|
||||
|
||||
if( nCmp != 0 )
|
||||
return nCmp < 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Copy the data from the specified schema shared object into this.
|
||||
// Both objects must be of the same type.
|
||||
//----------------------------------------------------------------------------
|
||||
void CSchemaSharedObjectBase::Copy( const CSharedObject & soRHS )
|
||||
{
|
||||
Assert( GetTypeID() == soRHS.GetTypeID() );
|
||||
CSchemaSharedObjectBase & soRHSBase = (CSchemaSharedObjectBase &)soRHS;
|
||||
*GetPObject() = *(soRHSBase.GetPObject());
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Dumps diagnostic information about the shared object
|
||||
//----------------------------------------------------------------------------
|
||||
void CSchemaSharedObjectBase::Dump() const
|
||||
{
|
||||
CSchemaSharedObjectHelper::Dump( GetPObject() );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Returns the record info object for this type of schema object
|
||||
//----------------------------------------------------------------------------
|
||||
const CRecordInfo * CSchemaSharedObjectBase::GetRecordInfo() const
|
||||
{
|
||||
return GetPObject()->GetPRecordInfo();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Claims all memory for the object.
|
||||
//----------------------------------------------------------------------------
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
void CSchemaSharedObjectBase::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
CSharedObject::Validate( validator, pchName );
|
||||
|
||||
// these are INSIDE the function instead of outside so the interface
|
||||
// doesn't change
|
||||
VALIDATE_SCOPE();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
} // namespace GCSDK
|
||||
|
||||
|
||||
@@ -0,0 +1,999 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Database Backed Object caching and manipulation
|
||||
//
|
||||
//=============================================================================
|
||||
#include "stdafx.h"
|
||||
#include "sdocache.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
GCConVar s_ConVarSDOCacheLRULimitMB( "@SDOCacheLRULimitMB", "400", FCVAR_REPLICATED );
|
||||
GCConVar s_ConVarSDOCacheMaxMemcachedReadJobs( "@SDOCacheMaxMemcachedReadJobs", "4", FCVAR_REPLICATED );
|
||||
GCConVar s_ConVarSDOCacheMaxMemcachedReadBatchSize( "@SDOCacheMaxMemcachedReadBatchSize", "100", FCVAR_REPLICATED );
|
||||
GCConVar s_ConVarSDOCacheMaxSQLReadJobsPerSDOType( "@SDOCacheMaxSQLReadJobsPerSDOType", "4", FCVAR_REPLICATED );
|
||||
GCConVar s_ConVarSDOCacheMaxSQLReadBatchSize( "@SDOCacheMaxSQLReadBatchSize", "100", FCVAR_REPLICATED );
|
||||
GCConVar s_ConVarSDOCacheMaxPendingSQLReads( "@SDOCacheMaxPendingSQLReads", "2000", FCVAR_REPLICATED );
|
||||
GCConVar s_ConVarSDOCacheMaxPendingMemcachedReads( "@SDOCacheMaxPendingMemcachedReads", "25000", FCVAR_REPLICATED );
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
// A string used to tell the difference between nil objects and actual objects in memcached
|
||||
const char k_rgchNilObjSerializedValue[] = "nilobj";
|
||||
|
||||
|
||||
// Global instance
|
||||
CSDOCache &GSDOCache()
|
||||
{
|
||||
static CSDOCache s_SDOCache;
|
||||
return s_SDOCache;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Creates a key name that looks like "Prefix_%u" but faster
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSDOCache::CreateSimpleMemcachedName( CFmtStr &strDest, const char *pchPrefix, uint32 unPrefixLen, uint32 unSuffix )
|
||||
{
|
||||
Assert( FMTSTR_STD_LEN - unPrefixLen > 10 + 1 );
|
||||
V_memcpy( strDest.Access(), pchPrefix, unPrefixLen );
|
||||
_i64toa( unSuffix, strDest.Access() + unPrefixLen, 10 );
|
||||
strDest.Access()[ FMTSTR_STD_LEN - 1 ] = NULL;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CSDOCache::CSDOCache() :
|
||||
m_cubLRUItems( 0 ),
|
||||
m_mapTypeStats( DefLessFunc( int ) )
|
||||
{
|
||||
memset( &m_StatsSDOCache, 0, sizeof( m_StatsSDOCache ) );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CSDOCache::~CSDOCache()
|
||||
{
|
||||
// delete all the entries from our cache
|
||||
FOR_EACH_MAP_FAST( m_mapISDOLoaded, iMap )
|
||||
{
|
||||
delete m_mapISDOLoaded.Key( iMap );
|
||||
}
|
||||
|
||||
FOR_EACH_MAP_FAST( m_mapQueuedRequests, iMap )
|
||||
{
|
||||
delete m_mapQueuedRequests.Key( iMap );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: registers an SDO type
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSDOCache::RegisterSDO( int nType, const char *pchName )
|
||||
{
|
||||
if ( !m_mapQueueSQLRequests.HasElement( nType ) )
|
||||
{
|
||||
m_mapQueueSQLRequests.Insert( nType, new SQLRequestManager_t );
|
||||
int iMap = m_mapTypeStats.Insert( nType );
|
||||
m_mapTypeStats[iMap].m_strName = pchName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: a SDO object has been referenced
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSDOCache::OnSDOReferenced( ISDO *pSDO )
|
||||
{
|
||||
// lookup where the SDO is, it has where we are in the LRU
|
||||
int iMap = m_mapISDOLoaded.Find( pSDO );
|
||||
Assert( m_mapISDOLoaded.IsValidIndex( iMap ) );
|
||||
if ( !m_mapISDOLoaded.IsValidIndex( iMap ) )
|
||||
return;
|
||||
|
||||
// move us out of the LRU
|
||||
if ( m_listLRU.IsValidIndex( m_mapISDOLoaded[iMap] ) )
|
||||
{
|
||||
RemoveSDOFromLRU( iMap );
|
||||
}
|
||||
else
|
||||
{
|
||||
// we may not have been in the LRU if it's a custom insert
|
||||
}
|
||||
|
||||
// Update stats
|
||||
int iMapStats = m_mapTypeStats.Find( pSDO->GetType() );
|
||||
if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
|
||||
{
|
||||
m_mapTypeStats[iMapStats].m_nRefed++;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: a SDO object has gone released
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSDOCache::OnSDOReleased( ISDO *pSDO )
|
||||
{
|
||||
// Update stats
|
||||
int iMapStats = m_mapTypeStats.Find( pSDO->GetType() );
|
||||
if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
|
||||
{
|
||||
m_mapTypeStats[iMapStats].m_nRefed--;
|
||||
}
|
||||
|
||||
// lookup where the SDO is, it has where we are in the LRU
|
||||
int iMap = m_mapISDOLoaded.Find( pSDO );
|
||||
if ( m_mapISDOLoaded.IsValidIndex( iMap ) )
|
||||
{
|
||||
// we shouldn't be in the LRU
|
||||
bool bInLRU = m_listLRU.IsValidIndex( m_mapISDOLoaded[iMap] );
|
||||
Assert( !bInLRU );
|
||||
if ( bInLRU )
|
||||
return;
|
||||
|
||||
// count the bytes and move us to the head of the LRU
|
||||
ISDO *pSDO = m_mapISDOLoaded.Key( iMap );
|
||||
LRUItem_t item = { pSDO, pSDO->CubBytesUsed() };
|
||||
m_mapISDOLoaded[iMap] = m_listLRU.AddToTail( item );
|
||||
m_cubLRUItems += item.m_cub;
|
||||
|
||||
if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
|
||||
{
|
||||
m_mapTypeStats[iMapStats].m_cubUnrefed += item.m_cub;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// it's actually valid it's not in the SDO cache anymore - could be an orphaned pointer
|
||||
if ( pSDO->GetRefCount() == 0 )
|
||||
{
|
||||
delete pSDO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: pulls a cached SDO from the right maps
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSDOCache::RemoveSDOFromLRU( int iMap )
|
||||
{
|
||||
int iLRU = m_mapISDOLoaded[iMap];
|
||||
|
||||
Assert( m_listLRU.IsValidIndex( iLRU ) );
|
||||
if ( !m_listLRU.IsValidIndex( iLRU ) )
|
||||
return;
|
||||
|
||||
int iMapStats = m_mapTypeStats.Find( m_listLRU[iLRU].m_pSDO->GetType() );
|
||||
if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
|
||||
{
|
||||
m_mapTypeStats[iMapStats].m_cubUnrefed -= m_listLRU[iLRU].m_cub;
|
||||
}
|
||||
|
||||
m_cubLRUItems -= m_listLRU[iLRU].m_cub;
|
||||
m_listLRU.Remove( iLRU );
|
||||
m_mapISDOLoaded[iMap] = m_listLRU.InvalidIndex();
|
||||
|
||||
// Update stats
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: writes a SDO to memcached
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSDOCache::WriteSDOToMemcached( ISDO *pSDO )
|
||||
{
|
||||
CFmtStr strKey;
|
||||
CUtlBuffer buf;
|
||||
|
||||
pSDO->GetMemcachedKeyName( strKey );
|
||||
pSDO->WriteToBuffer( buf );
|
||||
return GGCBase()->BMemcachedSet( strKey.Access(), buf );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: writes a SDO to memcached
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSDOCache::DeleteSDOFromMemcached( ISDO *pSDO )
|
||||
{
|
||||
CFmtStr strKey;
|
||||
pSDO->GetMemcachedKeyName( strKey );
|
||||
return GGCBase()->BMemcachedDelete( strKey.Access() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: runs the queued batch loader
|
||||
//-----------------------------------------------------------------------------
|
||||
class CGCJobLoadSDOSetFromMemcached : public CGCJob
|
||||
{
|
||||
public:
|
||||
CGCJobLoadSDOSetFromMemcached( CGCBase *pGC )
|
||||
: CGCJob( pGC )
|
||||
{
|
||||
}
|
||||
|
||||
void AddSDOToLoad( ISDO *pSDO, int iRequestID )
|
||||
{
|
||||
int i = m_vecRequests.AddToTail();
|
||||
m_vecRequests[i].m_pSDO = pSDO;
|
||||
m_vecRequests[i].m_iRequestID = iRequestID;
|
||||
}
|
||||
|
||||
bool BYieldingRunJob( void * )
|
||||
{
|
||||
Assert( m_vecRequests.Count() > 0 );
|
||||
if ( 0 == m_vecRequests.Count() )
|
||||
{
|
||||
GSDOCache().OnMemcachedLoadJobComplete( GetJobID() );
|
||||
return false;
|
||||
}
|
||||
|
||||
// get the names of all the items
|
||||
CUtlVector<CUtlString> vecKeys;
|
||||
vecKeys.AddMultipleToTail( m_vecRequests.Count() );
|
||||
FOR_EACH_VEC( m_vecRequests, i )
|
||||
{
|
||||
CFmtStr strKey;
|
||||
m_vecRequests[i].m_pSDO->GetMemcachedKeyName( strKey );
|
||||
vecKeys[i] = strKey;
|
||||
}
|
||||
|
||||
// ask in a batch from memcached
|
||||
CUtlVector<CGCBase::GCMemcachedGetResult_t> vecGetResults;
|
||||
bool bGetSuccess = m_pGC->BYieldingMemcachedGet( vecKeys, vecGetResults );
|
||||
|
||||
// go through each request looking up the results
|
||||
FOR_EACH_VEC( m_vecRequests, i )
|
||||
{
|
||||
if ( bGetSuccess && vecGetResults.IsValidIndex( i ) && vecGetResults[i].m_bKeyFound )
|
||||
{
|
||||
const CGCBase::GCMemcachedGetResult_t &result = vecGetResults[i];
|
||||
bool bNilObj = ( result.m_bufValue.Count() == sizeof( k_rgchNilObjSerializedValue )
|
||||
&& 0 == V_memcmp( result.m_bufValue.Base(), k_rgchNilObjSerializedValue, sizeof( k_rgchNilObjSerializedValue ) ) );
|
||||
|
||||
// we've loaded OK
|
||||
if ( bNilObj || ( result.m_bufValue.Count() && m_vecRequests[i].m_pSDO->BReadFromBuffer( result.m_bufValue.Base(), result.m_bufValue.Count() ) ) )
|
||||
{
|
||||
GSDOCache().OnSDOLoadSuccess( m_vecRequests[i].m_pSDO->GetType(), m_vecRequests[i].m_iRequestID, bNilObj, &m_vecRequests[i].m_pSDO );
|
||||
GSDOCache().GetStats().m_cItemsLoadedFromMemcached += 1;
|
||||
GSDOCache().GetStats().m_cNilItemsLoadedFromMemcached += bNilObj ? 1 : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// couldn't load; delete the entry
|
||||
m_pGC->BMemcachedDelete( vecKeys[i] );
|
||||
GSDOCache().OnMemcachedSDOLoadFailure( m_vecRequests[i].m_pSDO->GetType(), m_vecRequests[i].m_iRequestID );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// post back failure
|
||||
GSDOCache().OnMemcachedSDOLoadFailure( m_vecRequests[i].m_pSDO->GetType(), m_vecRequests[i].m_iRequestID );
|
||||
}
|
||||
}
|
||||
|
||||
GSDOCache().OnMemcachedLoadJobComplete( GetJobID() );
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Request_t
|
||||
{
|
||||
int m_iRequestID;
|
||||
ISDO *m_pSDO;
|
||||
};
|
||||
|
||||
CUtlVector<Request_t> m_vecRequests;
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: runs the queued batch loader
|
||||
//-----------------------------------------------------------------------------
|
||||
class CGCJobLoadSDOSetFromSQL : public CGCJob
|
||||
{
|
||||
public:
|
||||
CGCJobLoadSDOSetFromSQL( CGCBase *pGC, int eSDOType )
|
||||
: CGCJob( pGC ), m_eSDOType( eSDOType )
|
||||
{
|
||||
}
|
||||
|
||||
void AddSDOToLoad( ISDO *pSDO, int iRequestID )
|
||||
{
|
||||
m_vecPSDO.AddToTail( pSDO );
|
||||
m_vecRequestID.AddToTail( iRequestID );
|
||||
m_vecResults.AddToTail( false );
|
||||
}
|
||||
|
||||
bool BYieldingRunJob( void * )
|
||||
{
|
||||
Assert( m_vecPSDO.Count() == m_vecRequestID.Count() && m_vecPSDO.Count() == m_vecResults.Count() );
|
||||
Assert( m_vecPSDO.Count() > 0 );
|
||||
if ( 0 == m_vecPSDO.Count()
|
||||
|| m_vecPSDO.Count() != m_vecRequestID.Count()
|
||||
|| m_vecPSDO.Count() != m_vecResults.Count() )
|
||||
{
|
||||
GSDOCache().OnSQLLoadJobComplete( m_eSDOType, GetJobID() );
|
||||
return false;
|
||||
}
|
||||
|
||||
// use the first item to load the rest
|
||||
bool bSQLLayerSucceeded = m_vecPSDO[0]->BYldLoadFromSQL( m_vecPSDO, m_vecResults );
|
||||
|
||||
Assert( m_vecPSDO.Count() == m_vecRequestID.Count() && m_vecPSDO.Count() == m_vecResults.Count() );
|
||||
Assert( m_vecPSDO.Count() > 0 );
|
||||
if ( 0 == m_vecPSDO.Count()
|
||||
|| m_vecPSDO.Count() != m_vecRequestID.Count()
|
||||
|| m_vecPSDO.Count() != m_vecResults.Count() )
|
||||
{
|
||||
GSDOCache().OnSQLLoadJobComplete( m_eSDOType, GetJobID() );
|
||||
return false;
|
||||
}
|
||||
|
||||
// walk each result
|
||||
FOR_EACH_VEC( m_vecRequestID, i )
|
||||
{
|
||||
if ( bSQLLayerSucceeded )
|
||||
{
|
||||
// loaded, great
|
||||
GSDOCache().OnSDOLoadSuccess( m_eSDOType, m_vecRequestID[i], !m_vecResults[i], &m_vecPSDO[i] );
|
||||
GSDOCache().WriteSDOToMemcached( m_vecPSDO[i] );
|
||||
|
||||
GSDOCache().GetStats().m_cItemsLoadedFromSQL += 1;
|
||||
GSDOCache().GetStats().m_cNilItemsLoadedFromSQL += m_vecResults[i] ? 0 : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// no good, item couldn't load
|
||||
GSDOCache().OnSQLSDOLoadFailure( m_eSDOType, m_vecRequestID[i], bSQLLayerSucceeded );
|
||||
GSDOCache().GetStats().m_cItemsFailedLoadFromSQL += 1;
|
||||
}
|
||||
}
|
||||
|
||||
GSDOCache().OnSQLLoadJobComplete( m_eSDOType, GetJobID() );
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
int m_eSDOType;
|
||||
|
||||
// these objects all stay in sync
|
||||
// they need to be separate vectors so that they can be easily passed into other API's
|
||||
CUtlVector<ISDO *> m_vecPSDO;
|
||||
CUtlVector<int> m_vecRequestID;
|
||||
CUtlVector<bool> m_vecResults;
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: continues any jobs that were waiting on items
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSDOCache::BFrameFuncRunJobsUntilCompleted( CLimitTimer &limitTimer )
|
||||
{
|
||||
bool bDoneWork = false;
|
||||
|
||||
// continue any jobs
|
||||
while ( m_queueJobsToContinue.Count() && !limitTimer.BLimitReached() )
|
||||
{
|
||||
// pop the item off the head
|
||||
JobToWake_t jobToWake = m_queueJobsToContinue[ m_queueJobsToContinue.Head() ];
|
||||
m_queueJobsToContinue.Remove( m_queueJobsToContinue.Head() );
|
||||
GGCBase()->GetJobMgr().BRouteWorkItemCompletedIfExists( jobToWake.m_jobID, !jobToWake.m_bLoadLayerSuccess );
|
||||
}
|
||||
|
||||
// if we're over the limit, LRU an item
|
||||
while ( !limitTimer.BLimitReached() && m_cubLRUItems > (uint32)( s_ConVarSDOCacheLRULimitMB.GetInt() * k_nMegabyte ) && m_listLRU.Count() )
|
||||
{
|
||||
// pull off the last item in the LRU
|
||||
LRUItem_t item = m_listLRU[ m_listLRU.Head() ];
|
||||
|
||||
// kill the item
|
||||
int iMap = m_mapISDOLoaded.Find( item.m_pSDO );
|
||||
Assert( m_mapISDOLoaded.IsValidIndex( iMap ) );
|
||||
if ( m_mapISDOLoaded.IsValidIndex( iMap ) )
|
||||
{
|
||||
Assert( 0 == item.m_pSDO->GetRefCount() );
|
||||
if ( 0 == item.m_pSDO->GetRefCount() )
|
||||
{
|
||||
RemoveSDOFromLRU( iMap );
|
||||
|
||||
int iMapStats = m_mapTypeStats.Find( item.m_pSDO->GetType() );
|
||||
if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
|
||||
{
|
||||
if ( item.m_pSDO->BNilObject() )
|
||||
{
|
||||
m_mapTypeStats[iMapStats].m_nNilObjects--;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_mapTypeStats[iMapStats].m_nLoaded--;
|
||||
}
|
||||
}
|
||||
|
||||
m_mapISDOLoaded.RemoveAt( iMap );
|
||||
delete item.m_pSDO;
|
||||
}
|
||||
|
||||
m_StatsSDOCache.m_cItemsLRUd += 1;
|
||||
m_StatsSDOCache.m_cBytesLRUd += item.m_cub;
|
||||
}
|
||||
|
||||
bDoneWork = true;
|
||||
}
|
||||
|
||||
// return true if there is still work remaining
|
||||
return bDoneWork || m_queueJobsToContinue.Count() > 0;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: runs the queued batch loader
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSDOCache::BFrameFuncRunMemcachedQueriesUntilCompleted( CLimitTimer &limitTimer )
|
||||
{
|
||||
bool bDoneWork = false;
|
||||
|
||||
// batch load the set
|
||||
if ( m_queueMemcachedRequests.Count() )
|
||||
{
|
||||
// pass these off to a job
|
||||
if ( m_vecMemcachedJobs.Count() < s_ConVarSDOCacheMaxMemcachedReadJobs.GetInt() )
|
||||
{
|
||||
CGCJobLoadSDOSetFromMemcached *pJob = new CGCJobLoadSDOSetFromMemcached( GGCBase() );
|
||||
|
||||
// add a full batch to the job
|
||||
int cItemsInBatch = 0;
|
||||
while ( m_queueMemcachedRequests.Count() && cItemsInBatch < s_ConVarSDOCacheMaxMemcachedReadBatchSize.GetInt() )
|
||||
{
|
||||
m_StatsSDOCache.m_cQueuedMemcachedRequests--;
|
||||
int iMapQueuedRequest = m_queueMemcachedRequests[ m_queueMemcachedRequests.Head() ];
|
||||
m_queueMemcachedRequests.Remove( m_queueMemcachedRequests.Head() );
|
||||
Assert( m_mapQueuedRequests.IsValidIndex( iMapQueuedRequest ) );
|
||||
if ( m_mapQueuedRequests.IsValidIndex( iMapQueuedRequest ) )
|
||||
{
|
||||
cItemsInBatch++;
|
||||
pJob->AddSDOToLoad( m_mapQueuedRequests.Key( iMapQueuedRequest ), iMapQueuedRequest );
|
||||
}
|
||||
}
|
||||
|
||||
if ( cItemsInBatch > 0 )
|
||||
{
|
||||
m_StatMemcachedBatchSize.AddSample( cItemsInBatch * 100 );
|
||||
m_StatsSDOCache.m_nMemcachedBatchSizeAvgx100 = (int)m_StatMemcachedBatchSize.GetAveragedSample();
|
||||
|
||||
// add to the list
|
||||
m_vecMemcachedJobs.AddToTail( pJob->GetJobID() );
|
||||
|
||||
// start the job
|
||||
pJob->StartJob( NULL );
|
||||
|
||||
// mark that we should be ran again
|
||||
bDoneWork = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete pJob;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return if we still have work to do
|
||||
return bDoneWork;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: runs the queued batch loader
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSDOCache::BFrameFuncRunSQLQueriesUntilCompleted( CLimitTimer &limitTimer )
|
||||
{
|
||||
bool bDoneWork = false;
|
||||
|
||||
// loop through all items looking for batches to load
|
||||
FOR_EACH_MAP_FAST( m_mapQueueSQLRequests, iMapType )
|
||||
{
|
||||
// batch load the set
|
||||
SQLRequestManager_t *sqlRequestManager = m_mapQueueSQLRequests[iMapType];
|
||||
if ( sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Count() )
|
||||
{
|
||||
// pass these off to a job
|
||||
if ( sqlRequestManager->m_vecSQLJobs.Count() < s_ConVarSDOCacheMaxSQLReadJobsPerSDOType.GetInt() )
|
||||
{
|
||||
int nSDOType = m_mapQueueSQLRequests.Key( iMapType );
|
||||
CGCJobLoadSDOSetFromSQL *pJob = new CGCJobLoadSDOSetFromSQL( GGCBase(), nSDOType );
|
||||
|
||||
// add a full batch to the job
|
||||
int cItemsInBatch = 0;
|
||||
while ( sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Count() && cItemsInBatch < s_ConVarSDOCacheMaxSQLReadBatchSize.GetInt() )
|
||||
{
|
||||
m_StatsSDOCache.m_cQueuedSQLRequests--;
|
||||
int iMapQueuedRequest = sqlRequestManager->m_queueRequestIDsToLoadFromSQL[ sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Head() ];
|
||||
sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Remove( sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Head() );
|
||||
Assert( m_mapQueuedRequests.IsValidIndex( iMapQueuedRequest ) );
|
||||
if ( m_mapQueuedRequests.IsValidIndex( iMapQueuedRequest ) )
|
||||
{
|
||||
pJob->AddSDOToLoad( m_mapQueuedRequests.Key( iMapQueuedRequest ), iMapQueuedRequest );
|
||||
cItemsInBatch++;
|
||||
}
|
||||
}
|
||||
|
||||
if ( cItemsInBatch > 0 )
|
||||
{
|
||||
m_StatSQLBatchSize.AddSample( cItemsInBatch * 100 );
|
||||
m_StatsSDOCache.m_nSQLBatchSizeAvgx100 = (int)m_StatSQLBatchSize.GetAveragedSample();
|
||||
|
||||
// add to the list
|
||||
sqlRequestManager->m_vecSQLJobs.AddToTail( pJob->GetJobID() );
|
||||
|
||||
// start the job
|
||||
pJob->StartJob( NULL );
|
||||
bDoneWork = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete pJob;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update stats
|
||||
m_StatsSDOCache.m_cItemsInCache = m_mapISDOLoaded.Count();
|
||||
m_StatsSDOCache.m_cItemsQueuedToLoad = m_mapQueuedRequests.Count();
|
||||
m_StatsSDOCache.m_cBytesUnreferenced = m_cubLRUItems;
|
||||
m_StatsSDOCache.m_cItemsUnreferenced = m_listLRU.Count();
|
||||
|
||||
if ( m_StatsSDOCache.m_cItemsUnreferenced )
|
||||
{
|
||||
// estimate the total bytes from the size of the average size of an unreferenced item
|
||||
m_StatsSDOCache.m_cBytesInCacheEst = (m_StatsSDOCache.m_cItemsInCache * m_StatsSDOCache.m_cBytesUnreferenced) / m_StatsSDOCache.m_cItemsUnreferenced;
|
||||
}
|
||||
|
||||
// return true if we still have work to do
|
||||
return bDoneWork;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: queues a new item to try load from memcached
|
||||
//-----------------------------------------------------------------------------
|
||||
int CSDOCache::QueueMemcachedLoad( ISDO *pSDO )
|
||||
{
|
||||
int iMap = -1;
|
||||
if ( m_queueMemcachedRequests.Count() < s_ConVarSDOCacheMaxPendingMemcachedReads.GetInt() )
|
||||
{
|
||||
// insert a fresh item into the list
|
||||
iMap = m_mapQueuedRequests.Insert( pSDO );
|
||||
|
||||
// add the key to the queue
|
||||
m_queueMemcachedRequests.AddToTail( iMap );
|
||||
m_StatsSDOCache.m_cQueuedMemcachedRequests++;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_StatsSDOCache.m_cMemcachedRequestsRejectedTooBusy++;
|
||||
delete pSDO;
|
||||
}
|
||||
|
||||
return iMap;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: job results
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSDOCache::OnSDOLoadSuccess( int eSDOType, int iRequestID, bool bNilObj, ISDO **ppSDO )
|
||||
{
|
||||
Assert( m_mapQueuedRequests.IsValidIndex( iRequestID ) );
|
||||
if ( !m_mapQueuedRequests.IsValidIndex( iRequestID ) )
|
||||
return;
|
||||
|
||||
// set jobs waiting for the data to wake up
|
||||
CCopyableUtlVector<JobID_t> &vecJobs = m_mapQueuedRequests[iRequestID];
|
||||
FOR_EACH_VEC( vecJobs, i )
|
||||
{
|
||||
JobToWake_t jobToWake = { vecJobs[i], true /* success */ };
|
||||
m_queueJobsToContinue.AddToTail( jobToWake );
|
||||
}
|
||||
|
||||
// move from requested to loaded
|
||||
ISDO *pSDO = m_mapQueuedRequests.Key( iRequestID );
|
||||
m_mapQueuedRequests.RemoveAt( iRequestID );
|
||||
|
||||
// If the query succeeded but the object doesn't exist then we need to cache that
|
||||
if ( bNilObj )
|
||||
{
|
||||
ISDO *pInvalidSDO = pSDO->AllocNilObject();
|
||||
delete pSDO;
|
||||
pSDO = pInvalidSDO;
|
||||
*ppSDO = pSDO;
|
||||
}
|
||||
else
|
||||
{
|
||||
// do any extra initialization on the SDO
|
||||
pSDO->PostLoadInit();
|
||||
}
|
||||
|
||||
Assert( !m_mapISDOLoaded.HasElement( pSDO ) );
|
||||
int iMap = m_mapISDOLoaded.Insert( pSDO, m_listLRU.InvalidIndex() );
|
||||
|
||||
// update stats
|
||||
int iMapStats = m_mapTypeStats.Find( pSDO->GetType() );
|
||||
if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
|
||||
{
|
||||
if ( pSDO->BNilObject() )
|
||||
{
|
||||
m_mapTypeStats[iMapStats].m_nNilObjects++;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_mapTypeStats[iMapStats].m_nLoaded++;
|
||||
}
|
||||
}
|
||||
|
||||
// put us in the LRU - if it's just a hint, we may not be referenced immediately
|
||||
Assert( m_mapISDOLoaded.IsValidIndex( iMap ) );
|
||||
if ( m_mapISDOLoaded.IsValidIndex( iMap ) )
|
||||
{
|
||||
LRUItem_t item = { pSDO, pSDO->CubBytesUsed() };
|
||||
m_mapISDOLoaded[iMap] = m_listLRU.AddToTail( item );
|
||||
m_cubLRUItems += item.m_cub;
|
||||
|
||||
if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
|
||||
{
|
||||
m_mapTypeStats[iMapStats].m_cubUnrefed += item.m_cub;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: job results
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSDOCache::OnMemcachedSDOLoadFailure( int eSDOType, int iRequestID )
|
||||
{
|
||||
// we've failed to load an item from memcached - mark as needing load from SQL
|
||||
Assert( m_mapQueuedRequests.IsValidIndex( iRequestID ) );
|
||||
if ( !m_mapQueuedRequests.IsValidIndex( iRequestID ) )
|
||||
return;
|
||||
|
||||
Assert( eSDOType == m_mapQueuedRequests.Key( iRequestID )->GetType() );
|
||||
if ( eSDOType != m_mapQueuedRequests.Key( iRequestID )->GetType() )
|
||||
return;
|
||||
|
||||
int iSQLIndex = m_mapQueueSQLRequests.Find( eSDOType );
|
||||
AssertMsg1( m_mapQueueSQLRequests.IsValidIndex( iSQLIndex ), "Error, could not load SDO from SQL for unregistered type %d", eSDOType );
|
||||
if ( !m_mapQueueSQLRequests.IsValidIndex( iSQLIndex ) )
|
||||
return;
|
||||
|
||||
SQLRequestManager_t *sqlRequestManager = m_mapQueueSQLRequests[iSQLIndex];
|
||||
|
||||
if ( sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Count() < s_ConVarSDOCacheMaxPendingSQLReads.GetInt() )
|
||||
{
|
||||
sqlRequestManager->m_queueRequestIDsToLoadFromSQL.AddToTail( iRequestID );
|
||||
m_StatsSDOCache.m_cQueuedSQLRequests++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// too many outstanding items, reject and fail immediately
|
||||
m_StatsSDOCache.m_cSQLRequestsRejectedTooBusy++;
|
||||
OnSQLSDOLoadFailure( eSDOType, iRequestID, false /* loader failure */ );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: job results
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSDOCache::OnSQLSDOLoadFailure( int eSDOType, int iRequestID, bool bSQLLayerSucceeded )
|
||||
{
|
||||
Assert( m_mapQueuedRequests.IsValidIndex( iRequestID ) );
|
||||
if ( !m_mapQueuedRequests.IsValidIndex( iRequestID ) )
|
||||
return;
|
||||
|
||||
// failed to load from SQL
|
||||
// set jobs waiting for the data to wake up
|
||||
CCopyableUtlVector<JobID_t> &vecJobs = m_mapQueuedRequests[iRequestID];
|
||||
FOR_EACH_VEC( vecJobs, i )
|
||||
{
|
||||
JobToWake_t jobToWake = { vecJobs[i], bSQLLayerSucceeded };
|
||||
m_queueJobsToContinue.AddToTail( jobToWake );
|
||||
}
|
||||
|
||||
// kill the object - no one should have references to it, since it's only in the request list
|
||||
ISDO *pSDO = m_mapQueuedRequests.Key( iRequestID );
|
||||
m_mapQueuedRequests.RemoveAt( iRequestID );
|
||||
|
||||
Assert( 0 == pSDO->GetRefCount() );
|
||||
if ( 0 == pSDO->GetRefCount() )
|
||||
{
|
||||
delete pSDO;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: job results
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSDOCache::OnMemcachedLoadJobComplete( JobID_t jobID )
|
||||
{
|
||||
m_vecMemcachedJobs.FindAndRemove( jobID );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: job results
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSDOCache::OnSQLLoadJobComplete( int eSDOType, JobID_t jobID )
|
||||
{
|
||||
int iSQLIndex = m_mapQueueSQLRequests.Find( eSDOType );
|
||||
AssertMsg1( m_mapQueueSQLRequests.IsValidIndex( iSQLIndex ), "Error, could not load SDO from SQL for unregistered type %d", eSDOType );
|
||||
if ( !m_mapQueueSQLRequests.IsValidIndex( iSQLIndex ) )
|
||||
return;
|
||||
|
||||
SQLRequestManager_t *sqlRequestManager = m_mapQueueSQLRequests[iSQLIndex];
|
||||
sqlRequestManager->m_vecSQLJobs.FindAndRemove( jobID );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: deletes all unreferenced objects
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSDOCache::Flush()
|
||||
{
|
||||
int cReferencedObjects = 0;
|
||||
FOR_EACH_MAP_FAST( m_mapISDOLoaded, iMap )
|
||||
{
|
||||
if ( m_mapISDOLoaded.Key( iMap )->GetRefCount() == 0 )
|
||||
{
|
||||
int iMapStats = m_mapTypeStats.Find( m_mapISDOLoaded.Key( iMap )->GetType() );
|
||||
if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
|
||||
{
|
||||
if ( m_mapISDOLoaded.Key( iMap )->BNilObject() )
|
||||
{
|
||||
m_mapTypeStats[iMapStats].m_nNilObjects--;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_mapTypeStats[iMapStats].m_nLoaded--;
|
||||
}
|
||||
}
|
||||
|
||||
RemoveSDOFromLRU( iMap );
|
||||
DeleteSDOFromMemcached( m_mapISDOLoaded.Key( iMap ) );
|
||||
delete m_mapISDOLoaded.Key( iMap );
|
||||
m_mapISDOLoaded.RemoveAt( iMap );
|
||||
}
|
||||
else
|
||||
{
|
||||
cReferencedObjects++;
|
||||
}
|
||||
}
|
||||
|
||||
Assert( cReferencedObjects == 0 );
|
||||
Assert( m_cubLRUItems == 0 );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: returns the number of bytes we estimate we have referenced
|
||||
//-----------------------------------------------------------------------------
|
||||
int CSDOCache::CubReferencedEst()
|
||||
{
|
||||
return MAX( 0, (int)m_StatsSDOCache.m_cBytesInCacheEst - (int)m_StatsSDOCache.m_cBytesUnreferenced );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Prints information about the cache
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSDOCache::Dump()
|
||||
{
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "SDO cache:\n" );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Items Cached: %llu\n", m_StatsSDOCache.m_cItemsInCache );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Estimated Bytes Cached: %s\n", V_pretifymem( m_StatsSDOCache.m_cBytesInCacheEst, 2, true ) );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Loads Queued: %llu\n", m_StatsSDOCache.m_cItemsQueuedToLoad );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Memcached Loads Queued: %llu\n", m_StatsSDOCache.m_cQueuedMemcachedRequests );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " SQL Loads Queued: %llu\n", m_StatsSDOCache.m_cQueuedSQLRequests );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Items Unreferenced: %llu\n", m_StatsSDOCache.m_cItemsUnreferenced );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Bytes Unreferenced: %s\n", V_pretifymem( m_StatsSDOCache.m_cBytesUnreferenced, 2, true ) );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Items LRU'd: %llu\n", m_StatsSDOCache.m_cItemsLRUd );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Bytes LRU'd: %s\n", V_pretifymem( m_StatsSDOCache.m_cBytesLRUd, 2, true ) );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Items Loaded From Memcached: %llu\n", m_StatsSDOCache.m_cItemsLoadedFromMemcached );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Items Loaded From SQL: %llu\n", m_StatsSDOCache.m_cItemsLoadedFromSQL );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Nils Loaded From Memcached: %llu\n", m_StatsSDOCache.m_cNilItemsLoadedFromMemcached );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Nils Loaded From SQL: %llu\n", m_StatsSDOCache.m_cNilItemsLoadedFromSQL );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Average Memcached Batch: %f\n", (float)m_StatsSDOCache.m_nMemcachedBatchSizeAvgx100 / 100.0f );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Average SQL Batch: %f\n", (float)m_StatsSDOCache.m_nSQLBatchSizeAvgx100 / 100.0f );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Memcached Requests Rejected: %llu\n", m_StatsSDOCache.m_cMemcachedRequestsRejectedTooBusy );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " SQL Requests Rejected: %llu\n", m_StatsSDOCache.m_cSQLRequestsRejectedTooBusy );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Failed SQL Loads: %llu\n", m_StatsSDOCache.m_cItemsFailedLoadFromSQL );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\n" );
|
||||
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Per-type stats\n" );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS,
|
||||
"%-35s --- loaded --- referenced --- nil objects --- size (est)\n", "name" );
|
||||
|
||||
FOR_EACH_MAP_FAST( m_mapTypeStats, i )
|
||||
{
|
||||
int nLoaded = m_mapTypeStats[i].m_nLoaded + m_mapTypeStats[i].m_nNilObjects;
|
||||
char *pchRefed = "unknown";
|
||||
if ( m_mapTypeStats[i].m_nRefed < nLoaded && m_mapTypeStats[i].m_cubUnrefed > 0 )
|
||||
{
|
||||
pchRefed = V_pretifymem( ( ((int64)nLoaded * (int64)m_mapTypeStats[i].m_cubUnrefed) / ( nLoaded - m_mapTypeStats[i].m_nRefed ) ), 2, true );
|
||||
}
|
||||
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%-35s %11d %14d %15d %14s\n", m_mapTypeStats[i].m_strName.String(), m_mapTypeStats[i].m_nLoaded, m_mapTypeStats[i].m_nRefed, m_mapTypeStats[i].m_nNilObjects, pchRefed );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//**tempcomment** - This is good code. We'll hook it back up later
|
||||
//
|
||||
//static ConVar s_ConVarVerifyMemcacheDeletesBadEntries( "@VerifyMemcacheDeletesBadEntries", "1", FCVAR_REPLICATED );
|
||||
//
|
||||
////-----------------------------------------------------------------------------
|
||||
//// Purpose: verifies a set of memcached data against what's in SQL
|
||||
////-----------------------------------------------------------------------------
|
||||
//void CSDOCache::YldVerifyMemcachedData( CreateSDOFunc_t pCreateSDOFunc, CUtlVector<uint32> &vecIDs, int *pcMatches, int *pcMismatches )
|
||||
//{
|
||||
// // create the objects
|
||||
// CUtlVector<ISDO *> vecPSDOSQL;
|
||||
// CUtlVector<bool> vecSQLResults;
|
||||
// FOR_EACH_VEC( vecIDs, i )
|
||||
// {
|
||||
// vecPSDOSQL.AddToTail( pCreateSDOFunc( *this, vecIDs[i] ) );
|
||||
// vecSQLResults.AddToTail( false );
|
||||
// }
|
||||
//
|
||||
// CUtlVector<CUtlString> vecSKeysToDelete;
|
||||
//
|
||||
// // load them in a batch
|
||||
// bool bSQLLayerSucceeded = vecPSDOSQL[0]->BYldLoadFromSQL( vecIDs, vecPSDOSQL, vecSQLResults );
|
||||
// if ( bSQLLayerSucceeded )
|
||||
// {
|
||||
// // retrieve the memcache data
|
||||
// CUtlVector<CUtlString> vecSKeys;
|
||||
// FOR_EACH_VEC( vecPSDOSQL, i )
|
||||
// {
|
||||
// int iKey = vecSKeys.AddToTail();
|
||||
// vecPSDOSQL[i]->GetMemcachedKeyName( vecSKeys[iKey] );
|
||||
// }
|
||||
// CMemcachedAccess memcachedAccess( m_pServer->GetPMemcachedMgr() );
|
||||
// CUtlVector<MemcachedGetResult_t> vecMemcacheResults;
|
||||
// if ( memcachedAccess.BYldGetMulti( vecSKeys, vecMemcacheResults ) )
|
||||
// {
|
||||
// Assert( vecMemcacheResults.Count() == vecSQLResults.Count() );
|
||||
//
|
||||
// FOR_EACH_VEC( vecSQLResults, i )
|
||||
// {
|
||||
// bool bClearKey = false;
|
||||
// if ( vecSQLResults[i] && vecMemcacheResults[i].m_bKeyFound )
|
||||
// {
|
||||
// // compare the results
|
||||
// ISDO *pSDOCached = pCreateSDOFunc( *this, vecIDs[i] );
|
||||
// const CUtlAllocation &allocMemcache = vecMemcacheResults[i].m_bufValue;
|
||||
// if ( pSDOCached->BReadFromBuffer( allocMemcache.Base(), allocMemcache.Count() ) )
|
||||
// {
|
||||
// if ( pSDOCached->IsEqual( vecPSDOSQL[i] ) )
|
||||
// {
|
||||
// // cool
|
||||
// *pcMatches += 1;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // boo
|
||||
// *pcMismatches += 1;
|
||||
//
|
||||
// // print the differing bytes
|
||||
// /*
|
||||
// Msg( "key %s differs:\n", vecSKeys[i] );
|
||||
// for ( int n = 0; n < allocMemcache.Count(); n++ )
|
||||
// {
|
||||
// const byte *p1 = (byte*)bufSQL.Base() + n;
|
||||
// const byte *p2 = (byte*)bufCached.Base() + n;
|
||||
// if ( *p1 != *p2 )
|
||||
// {
|
||||
// Msg( " %3d: %2x %2x\n", n, *p1, *p2 );
|
||||
// }
|
||||
// }
|
||||
// */
|
||||
//
|
||||
// if ( s_ConVarVerifyMemcacheDeletesBadEntries.GetBool() )
|
||||
// bClearKey = true;
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // data failed to parse, clear
|
||||
// bClearKey = true;
|
||||
// }
|
||||
// delete pSDOCached;
|
||||
//
|
||||
// if ( bClearKey )
|
||||
// vecSKeysToDelete.AddToTail( vecSKeys[i] );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // SQL failure, ignore
|
||||
// }
|
||||
//
|
||||
// // clear any suspect keys
|
||||
// if ( vecSKeysToDelete.Count() )
|
||||
// {
|
||||
// CMemcachedAccess memcachedAccess( m_pServer->GetPMemcachedMgr() );
|
||||
// memcachedAccess.BAsyncDeleteMulti( vecSKeysToDelete );
|
||||
// }
|
||||
//
|
||||
// // delete all the SDO objects
|
||||
// FOR_EACH_VEC( vecPSDOSQL, i )
|
||||
// {
|
||||
// delete vecPSDOSQL[i];
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//</**tempcomment**> - This is good code. We'll hook it back up later
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: default comparison function - compares serialized versions
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CompareSDOObjects( ISDO *pSDO1, ISDO *pSDO2 )
|
||||
{
|
||||
CUtlBuffer b1, b2;
|
||||
pSDO1->WriteToBuffer( b1 );
|
||||
pSDO2->WriteToBuffer( b2 );
|
||||
|
||||
return ( b1.TellPut() == b2.TellPut() && 0 == Q_memcmp( b1.Base(), b2.Base(), b1.TellPut() ) );
|
||||
}
|
||||
|
||||
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: validates memory
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSDOCache::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
VALIDATE_SCOPE();
|
||||
|
||||
for ( int i = k_ESDOTypeInvalid+1; i < k_ESDOTypeMax; i++ )
|
||||
{
|
||||
SDOSet_t &SDOSet = m_rgSDOSet[i];
|
||||
ValidateObj( SDOSet.m_mapISDOLoaded );
|
||||
ValidateObj( SDOSet.m_mapQueuedRequests );
|
||||
ValidateObj( SDOSet.m_queueRequestIDsToLoadFromSQL );
|
||||
ValidateObj( SDOSet.m_vecSQLJobs );
|
||||
|
||||
FOR_EACH_MAP_FAST( SDOSet.m_mapISDOLoaded, iMap )
|
||||
{
|
||||
ValidatePtr( SDOSet.m_mapISDOLoaded[iMap].m_pSDO );
|
||||
}
|
||||
|
||||
FOR_EACH_MAP_FAST( SDOSet.m_mapQueuedRequests, iMap )
|
||||
{
|
||||
ValidatePtr( SDOSet.m_mapQueuedRequests[iMap].m_pSDO );
|
||||
ValidateObj( SDOSet.m_mapQueuedRequests[iMap].m_vecJobsWaiting );
|
||||
}
|
||||
}
|
||||
|
||||
ValidateObj( m_queueMemcachedRequests );
|
||||
ValidateObj( m_vecMemcachedJobs );
|
||||
ValidateObj( m_queueJobsToContinue );
|
||||
ValidateObj( m_listLRU );
|
||||
ValidateObj( m_StatMemcachedBatchSize );
|
||||
ValidateObj( m_StatSQLBatchSize );
|
||||
}
|
||||
#endif // DBGFLAG_VALIDATE
|
||||
|
||||
} // namespace GCSDK
|
||||
@@ -0,0 +1,560 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Base class for objects that are kept in synch between client and server
|
||||
//
|
||||
//=============================================================================
|
||||
#include "stdafx.h"
|
||||
#include "gcsdk_gcmessages.pb.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Map of all the factory functions for all CSharedObject classes
|
||||
//----------------------------------------------------------------------------
|
||||
CUtlMap<int, CSharedObject::SharedObjectInfo_t> CSharedObject::sm_mapFactories(DefLessFunc(int));
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Registers a new CSharedObject class
|
||||
//----------------------------------------------------------------------------
|
||||
void CSharedObject::RegisterFactory( int nTypeID, SOCreationFunc_t fnFactory, uint32 unFlags, const char *pchClassName )
|
||||
{
|
||||
SharedObjectInfo_t info;
|
||||
info.m_pFactoryFunction = fnFactory;
|
||||
info.m_unFlags = unFlags;
|
||||
info.m_pchClassName = pchClassName;
|
||||
info.m_sBuildCacheSubNodeName.Format( "BuildCacheSubscribed(%s)", pchClassName );
|
||||
info.m_sCreateNodeName.Format( "Create(%s)", pchClassName );
|
||||
info.m_sUpdateNodeName.Format( "Update(%s)", pchClassName );
|
||||
sm_mapFactories.InsertOrReplace( nTypeID, info );
|
||||
|
||||
//register this class with our SO stats as well
|
||||
#ifdef GC
|
||||
g_SharedObjectStats.RegisterSharedObjectType( nTypeID, pchClassName );
|
||||
#endif //GC
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Creates a new CSharedObject instance of the specified type ID
|
||||
//----------------------------------------------------------------------------
|
||||
CSharedObject *CSharedObject::Create( int nTypeID )
|
||||
{
|
||||
int nIndex = sm_mapFactories.Find( nTypeID );
|
||||
AssertMsg1( sm_mapFactories.IsValidIndex( nIndex ), "Probably failed to set object type (%d) on the server/client.\n", nTypeID );
|
||||
if( sm_mapFactories.IsValidIndex( nIndex ) )
|
||||
{
|
||||
return sm_mapFactories[nIndex].m_pFactoryFunction();
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Various accessors for static class data
|
||||
//----------------------------------------------------------------------------
|
||||
uint32 CSharedObject::GetTypeFlags( int nTypeID )
|
||||
{
|
||||
int nIndex = sm_mapFactories.Find( nTypeID );
|
||||
if( !sm_mapFactories.IsValidIndex( nIndex ) )
|
||||
return 0;
|
||||
else
|
||||
return sm_mapFactories[nIndex].m_unFlags;
|
||||
}
|
||||
|
||||
const char *CSharedObject::PchClassName( int nTypeID )
|
||||
{
|
||||
int nIndex = sm_mapFactories.Find( nTypeID );
|
||||
if( !sm_mapFactories.IsValidIndex( nIndex ) )
|
||||
return 0;
|
||||
else
|
||||
return sm_mapFactories[nIndex].m_pchClassName;
|
||||
|
||||
}
|
||||
|
||||
const char *CSharedObject::PchClassBuildCacheNodeName( int nTypeID )
|
||||
{
|
||||
int nIndex = sm_mapFactories.Find( nTypeID );
|
||||
if( !sm_mapFactories.IsValidIndex( nIndex ) )
|
||||
return 0;
|
||||
else
|
||||
return sm_mapFactories[nIndex].m_sBuildCacheSubNodeName.Get();
|
||||
}
|
||||
|
||||
const char *CSharedObject::PchClassCreateNodeName( int nTypeID )
|
||||
{
|
||||
int nIndex = sm_mapFactories.Find( nTypeID );
|
||||
if( !sm_mapFactories.IsValidIndex( nIndex ) )
|
||||
return 0;
|
||||
else
|
||||
return sm_mapFactories[nIndex].m_sCreateNodeName.Get();
|
||||
}
|
||||
|
||||
const char *CSharedObject::PchClassUpdateNodeName( int nTypeID )
|
||||
{
|
||||
int nIndex = sm_mapFactories.Find( nTypeID );
|
||||
if( !sm_mapFactories.IsValidIndex( nIndex ) )
|
||||
return 0;
|
||||
else
|
||||
return sm_mapFactories[nIndex].m_sUpdateNodeName.Get();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Figures out if the primary keys on these two objects are the same
|
||||
// using the subclass-defined less function
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSharedObject::BIsKeyEqual( const CSharedObject & soRHS ) const
|
||||
{
|
||||
// Make sure they are the same type.
|
||||
if ( GetTypeID() != soRHS.GetTypeID() )
|
||||
return false;
|
||||
|
||||
return !BIsKeyLess( soRHS ) && !soRHS.BIsKeyLess( *this );
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifdef GC
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Sends a create message for this shared object to the specified
|
||||
// steam ID.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSharedObject::BSendCreateToSteamID( const CSteamID & steamID, const CSteamID & steamIDOwner, uint64 ulVersion ) const
|
||||
{
|
||||
// Don't send these while shutting down. We'll use higher-level
|
||||
// connection-oriented messages instead
|
||||
if ( GGCBase()->GetIsShuttingDown() )
|
||||
return false;
|
||||
|
||||
CProtoBufMsg< CMsgSOSingleObject > msg( k_ESOMsg_Create );
|
||||
msg.Body().set_owner( steamIDOwner.ConvertToUint64() );
|
||||
msg.Body().set_type_id( GetTypeID() );
|
||||
msg.Body().set_version( ulVersion );
|
||||
if( !BAddToMessage( msg.Body().mutable_object_data() ) )
|
||||
{
|
||||
EmitWarning( SPEW_SHAREDOBJ, SPEW_ALWAYS, "Failed to add create fields to message in create" );
|
||||
return false;
|
||||
}
|
||||
|
||||
return GGCBase()->BSendGCMsgToClient( steamID, msg );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Sends a destroy message for this object to the specified steam ID
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSharedObject::BSendDestroyToSteamID( const CSteamID & steamID, const CSteamID & steamIDOwner, uint64 ulVersion ) const
|
||||
{
|
||||
// Don't send these while shutting down. We'll use higher-level
|
||||
// connection-oriented messages instead
|
||||
if ( GGCBase()->GetIsShuttingDown() )
|
||||
return false;
|
||||
|
||||
CProtoBufMsg< CMsgSOSingleObject > msg( k_ESOMsg_Destroy );
|
||||
msg.Body().set_owner( steamIDOwner.ConvertToUint64() );
|
||||
msg.Body().set_type_id( GetTypeID() );
|
||||
msg.Body().set_version( ulVersion );
|
||||
if( !BAddDestroyToMessage( msg.Body().mutable_object_data() ) )
|
||||
{
|
||||
EmitWarning( SPEW_SHAREDOBJ, SPEW_ALWAYS, "Failed to add key to message in create" );
|
||||
return false;
|
||||
}
|
||||
return GGCBase()->BSendGCMsgToClient( steamID, msg );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Wraps BYieldingAddInsertToTransaction with a transaction and a
|
||||
// commit.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSharedObject::BYieldingAddToDatabase()
|
||||
{
|
||||
CSQLAccess sqlAccess;
|
||||
sqlAccess.BBeginTransaction( CFmtStr( "CSharedObject::BYieldingAddToDatabase Type %d", GetTypeID() ) );
|
||||
if( !BYieldingAddInsertToTransaction( sqlAccess ) )
|
||||
{
|
||||
sqlAccess.RollbackTransaction();
|
||||
return false;
|
||||
}
|
||||
|
||||
return sqlAccess.BCommitTransaction();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Wraps BYieldingAddWriteToTransaction with a transaction and a
|
||||
// commit.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSharedObject::BYieldingWriteToDatabase( const CUtlVector< int > &fields )
|
||||
{
|
||||
CSQLAccess sqlAccess;
|
||||
sqlAccess.BBeginTransaction( CFmtStr( "CSharedObject::BYieldingWriteToDatabase Type %d", GetTypeID() ) );
|
||||
if( !BYieldingAddWriteToTransaction( sqlAccess, fields ) )
|
||||
{
|
||||
sqlAccess.RollbackTransaction();
|
||||
return false;
|
||||
}
|
||||
|
||||
if( sqlAccess.BCommitTransaction() )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Wraps BYieldingAddRemoveToTransaction with a transaction and a
|
||||
// commit.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSharedObject::BYieldingRemoveFromDatabase()
|
||||
{
|
||||
CSQLAccess sqlAccess;
|
||||
sqlAccess.BBeginTransaction( CFmtStr( "CSharedObject::BYieldingRemoveFromDatabase Type %d", GetTypeID() ) );
|
||||
if( !BYieldingAddRemoveToTransaction( sqlAccess ) )
|
||||
{
|
||||
sqlAccess.RollbackTransaction();
|
||||
return false;
|
||||
}
|
||||
|
||||
return sqlAccess.BCommitTransaction();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------------------
|
||||
// CSharedObjectStats
|
||||
//--------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
//our global stats for our SO objects
|
||||
CSharedObjectStats g_SharedObjectStats;
|
||||
|
||||
|
||||
CSharedObjectStats::CSharedObjectStats()
|
||||
: m_bCollectingStats( false )
|
||||
, m_nMicroSElapsed( 0 )
|
||||
{
|
||||
}
|
||||
|
||||
CSharedObjectStats::SOStats_t::SOStats_t()
|
||||
: m_nNumActive( 0 )
|
||||
{
|
||||
ResetStats();
|
||||
}
|
||||
|
||||
void CSharedObjectStats::SOStats_t::ResetStats()
|
||||
{
|
||||
m_nNumCreated = 0;
|
||||
m_nNumDestroyed = 0;
|
||||
m_nNumSends = 0;
|
||||
m_nRawBytesSent = 0;
|
||||
m_nMultiplexedBytesSent = 0;
|
||||
m_nNumSubOwner = 0;
|
||||
m_nNumSubOtherUsers = 0;
|
||||
m_nNumSubGameServer = 0;
|
||||
}
|
||||
|
||||
void CSharedObjectStats::StartCollectingStats()
|
||||
{
|
||||
//do nothing if already collecting
|
||||
if( m_bCollectingStats )
|
||||
return;
|
||||
|
||||
m_bCollectingStats = true;
|
||||
m_CollectTime.SetToJobTime();
|
||||
}
|
||||
|
||||
void CSharedObjectStats::StopCollectingStats()
|
||||
{
|
||||
if( !m_bCollectingStats )
|
||||
return;
|
||||
|
||||
m_bCollectingStats = false;
|
||||
m_nMicroSElapsed = m_CollectTime.CServerMicroSecsPassed();
|
||||
}
|
||||
|
||||
void CSharedObjectStats::RegisterSharedObjectType( int nTypeID, const char* pszTypeName )
|
||||
{
|
||||
if( nTypeID < 0 )
|
||||
{
|
||||
AssertMsg2( false, "Error registering shared object type %d (%s), negative type ID's are not supported", nTypeID, pszTypeName );
|
||||
return;
|
||||
}
|
||||
|
||||
//see if we need to grow our list to accommodate this type id
|
||||
if( nTypeID >= m_vTypeToIndex.Count() )
|
||||
{
|
||||
//make sure people aren't getting carried away with index ranges
|
||||
AssertMsg( nTypeID < 8 * 1024, "Warning: Using a very large type ID which can be inefficient for the shared object stats. Try to keep values to a smaller range" );
|
||||
int nOldSize = m_vTypeToIndex.Count();
|
||||
m_vTypeToIndex.AddMultipleToTail( nTypeID + 1 - nOldSize );
|
||||
for( int nCurrFill = nOldSize; nCurrFill < nTypeID; nCurrFill++ )
|
||||
{
|
||||
m_vTypeToIndex[ nCurrFill ] = knInvalidIndex;
|
||||
}
|
||||
}
|
||||
else if( m_vTypeToIndex[ nTypeID ] != knInvalidIndex )
|
||||
{
|
||||
//we have already registered something in this slot
|
||||
AssertMsg2( false, "Error registering shared object type %d (%s), may have multiple registrations of this type, check for conflicts", nTypeID, pszTypeName );
|
||||
return;
|
||||
}
|
||||
|
||||
//we need to register this type by adding a new record, and updating our index
|
||||
int nNewIndex = m_Stats.AddToTail();
|
||||
m_Stats[ nNewIndex ].m_sName = pszTypeName;
|
||||
m_Stats[ nNewIndex ].m_nTypeID = nTypeID;
|
||||
m_vTypeToIndex[ nTypeID ] = nNewIndex;
|
||||
}
|
||||
|
||||
void CSharedObjectStats::TrackSharedObjectLifetime( int nTypeID, int32 nCount )
|
||||
{
|
||||
uint16 nIndex = ( m_vTypeToIndex.IsValidIndex( nTypeID ) ) ? m_vTypeToIndex[ nTypeID ] : knInvalidIndex;
|
||||
if( nIndex != knInvalidIndex )
|
||||
{
|
||||
m_Stats[ nIndex ].m_nNumActive += nCount;
|
||||
}
|
||||
}
|
||||
|
||||
void CSharedObjectStats::TrackSharedObjectSendCreate( int nTypeID, uint32 nCount )
|
||||
{
|
||||
uint16 nIndex = ( m_vTypeToIndex.IsValidIndex( nTypeID ) ) ? m_vTypeToIndex[ nTypeID ] : knInvalidIndex;
|
||||
if( nIndex != knInvalidIndex )
|
||||
{
|
||||
m_Stats[ nIndex ].m_nNumCreated += nCount;
|
||||
}
|
||||
}
|
||||
|
||||
void CSharedObjectStats::TrackSharedObjectSendDestroy( int nTypeID, uint32 nCount )
|
||||
{
|
||||
uint16 nIndex = (m_vTypeToIndex.IsValidIndex( nTypeID ) ) ? m_vTypeToIndex[ nTypeID ] : knInvalidIndex;
|
||||
if( nIndex != knInvalidIndex )
|
||||
{
|
||||
m_Stats[ nIndex ].m_nNumDestroyed += nCount;
|
||||
}
|
||||
}
|
||||
|
||||
void CSharedObjectStats::TrackSubscription( int nTypeID, uint32 nFlags, uint32 nNumSubscriptions )
|
||||
{
|
||||
if( !m_bCollectingStats )
|
||||
return;
|
||||
|
||||
uint16 nIndex = ( m_vTypeToIndex.IsValidIndex( nTypeID ) ) ? m_vTypeToIndex[ nTypeID ] : knInvalidIndex;
|
||||
if( nIndex != knInvalidIndex )
|
||||
{
|
||||
if( nFlags & k_ESOFlag_SendToOwner )
|
||||
m_Stats[ nIndex ].m_nNumSubOwner += nNumSubscriptions;
|
||||
if( nFlags & k_ESOFlag_SendToOtherUsers )
|
||||
m_Stats[ nIndex ].m_nNumSubOtherUsers += nNumSubscriptions;
|
||||
if( nFlags & k_ESOFlag_SendToOtherGameservers )
|
||||
m_Stats[ nIndex ].m_nNumSubGameServer += nNumSubscriptions;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CSharedObjectStats::TrackSharedObjectSend( int nTypeID, uint32 nNumClients, uint32 nMsgSize )
|
||||
{
|
||||
if( !m_bCollectingStats )
|
||||
return;
|
||||
|
||||
uint16 nIndex = ( m_vTypeToIndex.IsValidIndex( nTypeID ) ) ? m_vTypeToIndex[ nTypeID ] : knInvalidIndex;
|
||||
if( nIndex != knInvalidIndex )
|
||||
{
|
||||
m_Stats[ nIndex ].m_nNumSends++;
|
||||
m_Stats[ nIndex ].m_nRawBytesSent += nMsgSize;
|
||||
m_Stats[ nIndex ].m_nMultiplexedBytesSent += nMsgSize * nNumClients;
|
||||
}
|
||||
}
|
||||
|
||||
void CSharedObjectStats::ResetStats()
|
||||
{
|
||||
FOR_EACH_VEC( m_Stats, nCurrSO )
|
||||
{
|
||||
m_Stats[ nCurrSO ].ResetStats();
|
||||
}
|
||||
}
|
||||
|
||||
bool CSharedObjectStats::SortSOStatsSent( const SOStats_t* pLhs, const SOStats_t* pRhs )
|
||||
{
|
||||
//highest bandwidth ones go up top
|
||||
if( pLhs->m_nMultiplexedBytesSent != pRhs->m_nMultiplexedBytesSent )
|
||||
return pLhs->m_nMultiplexedBytesSent > pRhs->m_nMultiplexedBytesSent;
|
||||
|
||||
//otherwise sort by the name
|
||||
return pLhs->m_nTypeID < pRhs->m_nTypeID;
|
||||
}
|
||||
|
||||
bool CSharedObjectStats::SortSOStatsSubscribe( const SOStats_t* pLhs, const SOStats_t* pRhs )
|
||||
{
|
||||
//sort based on total number of subscriptions
|
||||
uint32 nLhsSub = pLhs->m_nNumSubOwner + pLhs->m_nNumSubOtherUsers + pLhs->m_nNumSubGameServer;
|
||||
uint32 nRhsSub = pRhs->m_nNumSubOwner + pRhs->m_nNumSubOtherUsers + pRhs->m_nNumSubGameServer;
|
||||
if( nLhsSub != nRhsSub )
|
||||
return nLhsSub > nRhsSub;
|
||||
|
||||
//otherwise sort by the name
|
||||
return pLhs->m_nTypeID < pRhs->m_nTypeID;
|
||||
}
|
||||
|
||||
void CSharedObjectStats::ReportCollectedStats() const
|
||||
{
|
||||
//build up a list of our stats, so we can sort them appropriately (also total how many bytes we send)
|
||||
uint64 nTotalRawBytes = 0;
|
||||
uint64 nTotalMultiplexBytes = 0;
|
||||
uint32 nTotalActive = 0;
|
||||
uint32 nTotalCreates = 0;
|
||||
uint32 nTotalFrees = 0;
|
||||
uint32 nTotalSends = 0;
|
||||
uint32 nTotalSubOwner = 0;
|
||||
uint32 nTotalSubOtherUsers = 0;
|
||||
uint32 nTotalSubGameServer = 0;
|
||||
|
||||
CUtlVector< const SOStats_t* > sortedStats( 0, m_Stats.Count() );
|
||||
FOR_EACH_VEC( m_Stats, nCurrSO )
|
||||
{
|
||||
sortedStats.AddToTail( &( m_Stats[ nCurrSO ] ) );
|
||||
|
||||
nTotalRawBytes += m_Stats[ nCurrSO ].m_nRawBytesSent;
|
||||
nTotalMultiplexBytes += m_Stats[ nCurrSO ].m_nMultiplexedBytesSent;
|
||||
nTotalActive += m_Stats[ nCurrSO ].m_nNumActive;
|
||||
nTotalCreates += m_Stats[ nCurrSO ].m_nNumCreated;
|
||||
nTotalFrees += m_Stats[ nCurrSO ].m_nNumDestroyed;
|
||||
nTotalSends += m_Stats[ nCurrSO ].m_nNumSends;
|
||||
nTotalSubOwner += m_Stats[ nCurrSO ].m_nNumSubOwner;
|
||||
nTotalSubOtherUsers += m_Stats[ nCurrSO ].m_nNumSubOtherUsers;
|
||||
nTotalSubGameServer += m_Stats[ nCurrSO ].m_nNumSubGameServer;
|
||||
}
|
||||
|
||||
//determine the time scale to normalize this into per second measurements
|
||||
uint64 nMicroSElapsed = ( m_bCollectingStats ) ? m_CollectTime.CServerMicroSecsPassed() : m_nMicroSElapsed;
|
||||
double fSeconds = nMicroSElapsed / 1000000.0;
|
||||
double fToS = ( nMicroSElapsed > 0 ) ? 1000000.0 / nMicroSElapsed : 1.0;
|
||||
|
||||
//-----------------------------------------
|
||||
// Update stats
|
||||
|
||||
std::sort( sortedStats.begin(), sortedStats.end(), SortSOStatsSent );
|
||||
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\n" );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "SO Cache Transmits - %.2f second capture\n", fSeconds );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\n" );
|
||||
|
||||
//now run through and display our report
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%s", "Type Name SendKB % SendKB/S MPlex Create C/S Destroy D/S Update U/S Active U/S (%)\n" );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%s", "---- ------------------------------ ------- ------ -------- ------ ------- ------- ------- ------- ------- ------- --------- -------\n" );
|
||||
|
||||
FOR_EACH_VEC( sortedStats, nCurrSO )
|
||||
{
|
||||
const SOStats_t& stats = *sortedStats[ nCurrSO ];
|
||||
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%4d %-30s %7u %5.1f%% %8.1f %6.2f %7u %7.0f %7u %7.0f %7u %7.0f %9u %6.3f%%\n",
|
||||
stats.m_nTypeID,
|
||||
stats.m_sName.Get(),
|
||||
( uint32 )( stats.m_nRawBytesSent / 1024 ),
|
||||
100.0 * ( double )stats.m_nRawBytesSent / ( double )MAX( 1, nTotalRawBytes ),
|
||||
( double )( stats.m_nRawBytesSent / 1024 ) * fToS,
|
||||
( double )stats.m_nMultiplexedBytesSent / MAX( 1, stats.m_nRawBytesSent ),
|
||||
stats.m_nNumCreated,
|
||||
stats.m_nNumCreated * fToS,
|
||||
stats.m_nNumDestroyed,
|
||||
stats.m_nNumDestroyed * fToS,
|
||||
stats.m_nNumSends,
|
||||
stats.m_nNumSends * fToS,
|
||||
stats.m_nNumActive,
|
||||
100.0 * stats.m_nNumSends * fToS / MAX( 1, stats.m_nNumActive ) );
|
||||
}
|
||||
|
||||
//close it out with the totals
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%s", "---- ------------------------------ ------- ------ -------- ------ ------- ------- ------- ------- ------- ------- --------- -------\n" );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Totals %7u %5.1f%% %8.1f %6.2f %7u %7.0f %7u %7.0f %7u %7.0f %9u %6.3f%%\n",
|
||||
( uint32 )( nTotalRawBytes / 1024 ),
|
||||
100.0,
|
||||
( double )( nTotalRawBytes / 1024 ) * fToS,
|
||||
( double )nTotalMultiplexBytes / ( double )MAX( 1, nTotalRawBytes ),
|
||||
nTotalCreates,
|
||||
nTotalCreates * fToS,
|
||||
nTotalFrees,
|
||||
nTotalFrees * fToS,
|
||||
nTotalSends,
|
||||
nTotalSends * fToS,
|
||||
nTotalActive,
|
||||
100.0 * nTotalSends * fToS / MAX( 1, nTotalActive ) );
|
||||
|
||||
//-----------------------------------------
|
||||
// Subscription stats
|
||||
|
||||
std::sort( sortedStats.begin(), sortedStats.end(), SortSOStatsSubscribe );
|
||||
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\n" );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "SO Cache Subscriptions Sends - %.2f second capture\n", fSeconds );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\n" );
|
||||
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%s", "Type Name Owner Owner/S Other Other/S Server Server/S\n" );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%s", "---- ------------------------------ -------- -------- -------- -------- -------- --------\n" );
|
||||
|
||||
FOR_EACH_VEC( sortedStats, nCurrSO )
|
||||
{
|
||||
const SOStats_t& stats = *sortedStats[ nCurrSO ];
|
||||
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%4d %-30s %8u %8.0f %8u %8.0f %8u %8.0f\n",
|
||||
stats.m_nTypeID,
|
||||
stats.m_sName.Get(),
|
||||
stats.m_nNumSubOwner,
|
||||
stats.m_nNumSubOwner * fToS,
|
||||
stats.m_nNumSubOtherUsers,
|
||||
stats.m_nNumSubOtherUsers * fToS,
|
||||
stats.m_nNumSubGameServer,
|
||||
stats.m_nNumSubGameServer * fToS );
|
||||
}
|
||||
|
||||
//close it out with the totals
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%s", "---- ------------------------------ -------- -------- -------- -------- -------- --------\n" );
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Totals %8u %8.0f %8u %8.0f %8u %8.0f\n",
|
||||
nTotalSubOwner,
|
||||
nTotalSubOwner * fToS,
|
||||
nTotalSubOtherUsers,
|
||||
nTotalSubOtherUsers * fToS,
|
||||
nTotalSubGameServer,
|
||||
nTotalSubGameServer * fToS );
|
||||
|
||||
}
|
||||
|
||||
#endif // GC
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Claims all the memory for the shared object
|
||||
//----------------------------------------------------------------------------
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
void CSharedObject::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
VALIDATE_SCOPE();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Claims all the static memory for the shared object (i.e. the
|
||||
// factory function map.
|
||||
//----------------------------------------------------------------------------
|
||||
void CSharedObject::ValidateStatics( CValidator & validator )
|
||||
{
|
||||
CValidateAutoPushPop validatorAutoPushPop( validator, NULL, "CSharedObject::ValidateStatics", "CSharedObject::ValidateStatics" );
|
||||
ValidateObj( sm_mapFactories );
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
} // namespace GCSDK
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,397 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: A cache of a bunch of CSharedObjects
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include "stdafx.h"
|
||||
#include <time.h>
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
#ifdef GC
|
||||
static GCConVar add_object_clean_do_has_element( "add_object_clean_do_has_element", "0", 0, "Enables AddObjectClean() checking that the cache doesn't already have this pointer" );
|
||||
#endif
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//----------------------------------------------------------------------------
|
||||
CSharedObjectTypeCache::CSharedObjectTypeCache( int nTypeID )
|
||||
: m_nTypeID( nTypeID )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//----------------------------------------------------------------------------
|
||||
CSharedObjectTypeCache::~CSharedObjectTypeCache()
|
||||
{
|
||||
for ( int i = 0; i < m_vecObjects.Count(); i++ )
|
||||
{
|
||||
// NULL the entry so that this SO isn't found during
|
||||
// cleanup assertion checking.
|
||||
CSharedObject *pObj = m_vecObjects[ i ];
|
||||
m_vecObjects[ i ] = NULL;
|
||||
|
||||
#ifdef GC
|
||||
if ( pObj->BShouldDeleteByCache() )
|
||||
{
|
||||
#if ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
|
||||
--pObj->m_nRefCount;
|
||||
AssertMsg1( pObj->m_nRefCount == 0, "Destroying shared object %s that's still in use!", pObj->GetDebugString().String() );
|
||||
#endif // ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
|
||||
delete pObj;
|
||||
}
|
||||
#else
|
||||
delete pObj;
|
||||
#endif
|
||||
}
|
||||
m_vecObjects.Purge();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Common shared add-to-cache code shared between AddObject() and
|
||||
// AddObjectClean().
|
||||
//----------------------------------------------------------------------------
|
||||
void CSharedObjectTypeCache::AddObjectInternal( CSharedObject *pObject )
|
||||
{
|
||||
Assert( pObject );
|
||||
|
||||
m_vecObjects.AddToTail( pObject );
|
||||
#ifdef GC
|
||||
#if ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
|
||||
AssertMsg1( pObject->m_nRefCount >= 0, "AddObjectInternal(): Invalid ref count for shared object %s", pObject->GetDebugString().String() );
|
||||
++pObject->m_nRefCount;
|
||||
#endif // ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
|
||||
#endif
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Adds a shared object of the appropriate type to this type cache.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSharedObjectTypeCache::AddObject( CSharedObject *pObject )
|
||||
{
|
||||
Assert( pObject );
|
||||
Assert( m_nTypeID == pObject->GetTypeID() );
|
||||
if( m_vecObjects.HasElement( pObject ) )
|
||||
return false;
|
||||
|
||||
AddObjectInternal( pObject );
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Adds an object without dirtying. This is done when the object
|
||||
// is just being loaded from SQL or memcached, so it's safe not to do the
|
||||
// has element check.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSharedObjectTypeCache::AddObjectClean( CSharedObject *pObject )
|
||||
{
|
||||
Assert( m_nTypeID == pObject->GetTypeID() );
|
||||
|
||||
#ifdef GC
|
||||
if ( add_object_clean_do_has_element.GetBool() )
|
||||
{
|
||||
Assert( !m_vecObjects.HasElement( pObject ) );
|
||||
if( m_vecObjects.HasElement( pObject ) )
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
AddObjectInternal( pObject );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Destroys the object matching the one passed in. This could be the
|
||||
// same one or simply one with matching index fields.
|
||||
//----------------------------------------------------------------------------
|
||||
CSharedObject *CSharedObjectTypeCache::RemoveObject( const CSharedObject & soIndex )
|
||||
{
|
||||
Assert( m_nTypeID == soIndex.GetTypeID() ); // This is probably harmless, but it's most likely a bug
|
||||
int nIndex = FindSharedObjectIndex( soIndex );
|
||||
if( m_vecObjects.IsValidIndex( nIndex ) )
|
||||
{
|
||||
return RemoveObjectByIndex( nIndex );
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
CSharedObject *CSharedObjectTypeCache::RemoveObjectByIndex( uint32 nObj )
|
||||
{
|
||||
CSharedObject *pObj = m_vecObjects[nObj];
|
||||
#ifdef GC
|
||||
#if ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
|
||||
AssertMsg1( pObj->m_nRefCount > 0, "Invalid ref count for shared object %s", pObj->GetDebugString().String() );
|
||||
--pObj->m_nRefCount;
|
||||
#endif // ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
|
||||
#endif // GC
|
||||
m_vecObjects.Remove( nObj );
|
||||
return pObj;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Empties the object lists and deletes all elements
|
||||
//----------------------------------------------------------------------------
|
||||
void CSharedObjectTypeCache::DestroyAllObjects()
|
||||
{
|
||||
for ( int i = 0; i < m_vecObjects.Count(); i++ )
|
||||
{
|
||||
#ifdef GC
|
||||
if ( m_vecObjects[i]->BShouldDeleteByCache() )
|
||||
{
|
||||
#if ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
|
||||
--m_vecObjects[i]->m_nRefCount;
|
||||
AssertMsg1( m_vecObjects[i]->m_nRefCount == 0, "Destroying shared object %s that's still in use!", m_vecObjects[i]->GetDebugString().String() );
|
||||
#endif // ENABLE_SO_CONSTRUCT_DESTRUCT_PARANOIA
|
||||
delete m_vecObjects[i];
|
||||
}
|
||||
#else
|
||||
delete m_vecObjects[i];
|
||||
#endif
|
||||
}
|
||||
m_vecObjects.Purge();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Empties the object lists but doesn't delete any of the objects
|
||||
//----------------------------------------------------------------------------
|
||||
void CSharedObjectTypeCache::RemoveAllObjectsWithoutDeleting()
|
||||
{
|
||||
m_vecObjects.RemoveAll();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Makes sure there's room in the object vector for the suggested
|
||||
// number of items
|
||||
//----------------------------------------------------------------------------
|
||||
void CSharedObjectTypeCache::EnsureCapacity( uint32 nItems )
|
||||
{
|
||||
m_vecObjects.EnsureCapacity( nItems );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Searches the object list for an object that matches the provided
|
||||
// object on its index fields.
|
||||
//----------------------------------------------------------------------------
|
||||
CSharedObject *CSharedObjectTypeCache::FindSharedObject( const CSharedObject & soIndex )
|
||||
{
|
||||
int nIndex = FindSharedObjectIndex( soIndex );
|
||||
if( m_vecObjects.IsValidIndex( nIndex ) )
|
||||
return m_vecObjects[nIndex];
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Searches the object list for an object that matches the provided
|
||||
// object on its index fields.
|
||||
//----------------------------------------------------------------------------
|
||||
int CSharedObjectTypeCache::FindSharedObjectIndex( const CSharedObject & soIndex ) const
|
||||
{
|
||||
FOR_EACH_VEC( m_vecObjects, nObj )
|
||||
{
|
||||
if( m_vecObjects[nObj]->BIsKeyEqual( soIndex ) )
|
||||
return nObj;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Dumps all the objects in the type cache
|
||||
//----------------------------------------------------------------------------
|
||||
void CSharedObjectTypeCache::Dump() const
|
||||
{
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\tTypeCache for %d (%d objects):\n", GetTypeID(), m_vecObjects.Count() );
|
||||
FOR_EACH_VEC( m_vecObjects, nObj )
|
||||
{
|
||||
m_vecObjects[nObj]->Dump();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Claims all the memory for the cache and its objects
|
||||
//----------------------------------------------------------------------------
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
void CSharedObjectTypeCache::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
VALIDATE_SCOPE();
|
||||
ValidateObj( m_vecObjects );
|
||||
|
||||
FOR_EACH_VEC( m_vecObjects, nIndex )
|
||||
{
|
||||
m_vecObjects[nIndex]->Validate( validator, "m_vecObjects[n]" );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//----------------------------------------------------------------------------
|
||||
CSharedObjectCache::CSharedObjectCache( )
|
||||
: m_mapObjects( DefLessFunc(int) )
|
||||
, m_ulVersion( 0 )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//----------------------------------------------------------------------------
|
||||
CSharedObjectCache::~CSharedObjectCache()
|
||||
{
|
||||
FOR_EACH_MAP( m_mapObjects, nTypeIndex )
|
||||
{
|
||||
delete m_mapObjects[nTypeIndex];
|
||||
}
|
||||
m_mapObjects.Purge();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Returns the type cache for the specified type ID, returning NULL
|
||||
// if the cache didn't previously exist.
|
||||
//----------------------------------------------------------------------------
|
||||
CSharedObjectTypeCache *CSharedObjectCache::FindBaseTypeCache( int nClassID ) const
|
||||
{
|
||||
int nIndex = m_mapObjects.Find( nClassID );
|
||||
CSharedObjectTypeCache *pTypeCache = NULL;
|
||||
if( m_mapObjects.IsValidIndex( nIndex ) )
|
||||
{
|
||||
pTypeCache = m_mapObjects[nIndex];
|
||||
}
|
||||
return pTypeCache;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Returns the type cache for the specified type ID, creating a new
|
||||
// cache and returning it if one didn't previously exist. Never intended
|
||||
// to return NULL.
|
||||
//----------------------------------------------------------------------------
|
||||
CSharedObjectTypeCache *CSharedObjectCache::CreateBaseTypeCache( int nClassID )
|
||||
{
|
||||
//see if we already have an existing one
|
||||
CSharedObjectTypeCache *pCache = FindBaseTypeCache( nClassID );
|
||||
if( pCache )
|
||||
return pCache;
|
||||
|
||||
//nope, need to create one
|
||||
CSharedObjectTypeCache* pTypeCache = AllocateTypeCache( nClassID );
|
||||
m_mapObjects.Insert( nClassID, pTypeCache );
|
||||
#if 0
|
||||
// Kyle says: this is the newer way of managing caches on Dota but we haven't
|
||||
// brought any of it over yet
|
||||
m_CacheObjects.AddToTail( pTypeCache );
|
||||
//sort this cache for faster access
|
||||
std::sort( m_CacheObjects.begin(), m_CacheObjects.end(), SortCacheByTypeID );
|
||||
#endif
|
||||
return pTypeCache;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Adds a shared object to the cache.
|
||||
//----------------------------------------------------------------------------
|
||||
bool CSharedObjectCache::AddObject( CSharedObject *pSharedObject )
|
||||
{
|
||||
CSharedObjectTypeCache *pTypeCache = CreateBaseTypeCache( pSharedObject->GetTypeID() );
|
||||
if ( !pTypeCache->AddObject( pSharedObject ) )
|
||||
return false;
|
||||
|
||||
MarkDirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Removes the object matching the one passed in from this cache,
|
||||
// without destroying the actual object.
|
||||
//----------------------------------------------------------------------------
|
||||
CSharedObject *CSharedObjectCache::RemoveObject( const CSharedObject & soIndex )
|
||||
{
|
||||
CSharedObjectTypeCache *pTypeCache = FindBaseTypeCache( soIndex.GetTypeID() );
|
||||
if( !pTypeCache )
|
||||
return NULL;
|
||||
|
||||
MarkDirty();
|
||||
|
||||
return pTypeCache->RemoveObject( soIndex );
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Empties the object lists but doesn't delete any of the objects
|
||||
//----------------------------------------------------------------------------
|
||||
void CSharedObjectCache::RemoveAllObjectsWithoutDeleting()
|
||||
{
|
||||
FOR_EACH_MAP_FAST( m_mapObjects, nType )
|
||||
{
|
||||
m_mapObjects[nType]->RemoveAllObjectsWithoutDeleting();
|
||||
}
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Searches the object list for an object that matches the provided
|
||||
// object on its index fields.
|
||||
//----------------------------------------------------------------------------
|
||||
CSharedObject *CSharedObjectCache::FindSharedObject( const CSharedObject & soIndex )
|
||||
{
|
||||
CSharedObjectTypeCache *pTypeCache = FindBaseTypeCache( soIndex.GetTypeID() );
|
||||
if( pTypeCache )
|
||||
return pTypeCache->FindSharedObject( soIndex );
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Dumps all the objects in the type cache
|
||||
//----------------------------------------------------------------------------
|
||||
void CSharedObjectCache::Dump() const
|
||||
{
|
||||
EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "SharedObjectCache for %s (%d types):\n", GetOwner().Render(), m_mapObjects.Count() );
|
||||
FOR_EACH_MAP( m_mapObjects, nTypeIndex )
|
||||
{
|
||||
m_mapObjects[nTypeIndex]->Dump();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Purpose: Claims all the memory for the cache
|
||||
//----------------------------------------------------------------------------
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
void CSharedObjectCache::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
VALIDATE_SCOPE();
|
||||
|
||||
ValidateObj( m_mapObjects );
|
||||
FOR_EACH_MAP( m_mapObjects, nTypeIndex )
|
||||
{
|
||||
m_mapObjects[nTypeIndex]->Validate( validator, "m_mapObjects[n]" );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
} // namespace GCSDK
|
||||
@@ -0,0 +1,819 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "sharedobjecttransaction.h"
|
||||
|
||||
#include "sqlaccess/sqlaccess.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
class CTrustedHelper_OutputAndSetErrorState
|
||||
{
|
||||
public:
|
||||
CTrustedHelper_OutputAndSetErrorState( CSharedObjectTransactionEx& SharedObjectTransaction, const char *pszFunctionContext, const CSteamID& CacheOwnerSteamID, const CSharedObject *pObject )
|
||||
: m_SharedObjectTransaction( SharedObjectTransaction )
|
||||
, m_pszFunctionContext( pszFunctionContext )
|
||||
, m_CacheOwnerSteamID( CacheOwnerSteamID )
|
||||
, m_pObject( pObject )
|
||||
{
|
||||
}
|
||||
|
||||
void operator()( const bool bExpResult, const char *pszExp ) const
|
||||
{
|
||||
if ( !bExpResult )
|
||||
{
|
||||
AssertMsg4( bExpResult, "Failed verification: %s (context '%s'; owner '%s'; object '%s')", pszExp, m_pszFunctionContext, m_CacheOwnerSteamID.Render(), m_pObject ? m_pObject->GetDebugString().String() : "[none]" );
|
||||
m_SharedObjectTransaction.SetErrorState();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
CSharedObjectTransactionEx& m_SharedObjectTransaction;
|
||||
const char *m_pszFunctionContext;
|
||||
const CSteamID& m_CacheOwnerSteamID;
|
||||
const CSharedObject *m_pObject;
|
||||
};
|
||||
|
||||
#define CSOTVerifyBase( exp_, obj_ ) \
|
||||
CVerifyIfTrustedHelper( CTrustedHelper_OutputAndSetErrorState( *this, __FUNCTION__, m_pLockedSOCache->GetOwner(), obj_ ), (exp_), #exp_ ).GetResult()
|
||||
|
||||
#define CSOTVerify( exp_ ) \
|
||||
CSOTVerifyBase( exp_, NULL )
|
||||
|
||||
#define CSOTVerifyObj( exp_ ) \
|
||||
CSOTVerifyBase( exp_, pObject )
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
CSharedObjectTransactionEx::CSharedObjectTransactionEx( CGCSharedObjectCache *pLockedSOCache, const char *pszTransactionName )
|
||||
: m_pLockedSOCache( pLockedSOCache )
|
||||
, m_pSQLAccess( &m_sqlAccessInternal )
|
||||
{
|
||||
Assert( pszTransactionName );
|
||||
Assert( pszTransactionName[0] );
|
||||
Assert( m_pLockedSOCache );
|
||||
|
||||
// We have to start a SQL transaction no matter what. If we don't do this, then
|
||||
// any code internally or externally that tries to add operations to the open transaction
|
||||
// will instead run immediately. We Verify() here because we know the only thing that
|
||||
// can fail beginning a new transaction is to already be in a transaction, and that
|
||||
// can't be the case because we just made this object.
|
||||
m_bTransactionBuildSuccess = m_pSQLAccess->BBeginTransaction( pszTransactionName );
|
||||
Verify( m_bTransactionBuildSuccess );
|
||||
|
||||
// Grab another lock on our user that owns the cache we got passed in. This means that,
|
||||
// barring any maliscious or really terrible code happening on the outside, even if our
|
||||
// calling code unlocks *their* lock, we'll still have ours and can access the cache safely.
|
||||
Verify( GGCBase()->BLockSteamIDImmediate( m_pLockedSOCache->GetOwner() ) );
|
||||
|
||||
// Because we just constructed a fresh object, we want to guarantee that our internal state
|
||||
// is consistent. This way, either we're guaranteed to fail construction or we know moving
|
||||
// forward that we started in a good place so anything that's wrong since then is some kind
|
||||
// of real programmer error.
|
||||
Verify( BIsValidInternalState() );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
CSharedObjectTransactionEx::~CSharedObjectTransactionEx()
|
||||
{
|
||||
// If we fall off the stack and we haven't been submitted, manually rollback whatever work
|
||||
// we queued up in SQL and free up our local storage. This isn't an error.
|
||||
if ( m_pSQLAccess->BInTransaction() )
|
||||
{
|
||||
Rollback();
|
||||
}
|
||||
|
||||
// We're finally done manipulating this user's cache. If we're practicing best
|
||||
// practices for locking, this will still leave us locked in our outer scope. If we're
|
||||
// not, at least we'll be safe.
|
||||
GGCBase()->UnlockSteamID( m_pLockedSOCache->GetOwner() );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSharedObjectTransactionEx::BIsValidInternalState()
|
||||
{
|
||||
// Sanity check basic internal data.
|
||||
Assert( m_pSQLAccess );
|
||||
|
||||
// If we've done something bad with our transaction (ie., committed from outside, or
|
||||
// tried to submit through this object) and we're still doing work, that's a case we
|
||||
// can't handle.
|
||||
if ( !CSOTVerify( m_pSQLAccess->BInTransaction() ) )
|
||||
return false;
|
||||
|
||||
// Verify we have a cache and it's a cache we understand how to manipulate.
|
||||
if ( !CSOTVerify( m_pLockedSOCache ) || !CSOTVerify( m_pLockedSOCache->GetOwner().IsValid() ) || !CSOTVerify( m_pLockedSOCache->GetOwner().BIndividualAccount() ) )
|
||||
return false;
|
||||
|
||||
// Transactions can yield when trying to commit, so we need to be running a job. We also want
|
||||
// to be paranoid and make sure that the lock is actively held by our current job.
|
||||
AssertRunningJob();
|
||||
|
||||
if ( !CSOTVerify( GGCBase()->IsSteamIDLockedByJob( m_pLockedSOCache->GetOwner(), &GJobCur() ) ) )
|
||||
return false;
|
||||
|
||||
// Internal state is such that we can at least try to perform operations, at least.
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSharedObjectTransactionEx::BIsValidInput( const CSharedObject *pObject )
|
||||
{
|
||||
if ( !BIsValidInternalState() )
|
||||
return false;
|
||||
|
||||
if ( !CSOTVerifyObj( pObject ) )
|
||||
return false;
|
||||
|
||||
// Make sure we have a valid type ID for this object. There aren't any objects that return negative
|
||||
// IDs, but if we pass in a deleted or bogus pointer, we'll probably crash here when we hit the vtable.
|
||||
if ( !CSOTVerifyObj( pObject->GetTypeID() > 0 ) )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSharedObjectTransactionEx::BTrackModifiedObjectInternal( CSharedObject *pObject, CSharedObject **out_ppWritableObject )
|
||||
{
|
||||
if ( !BIsValidInput( pObject ) )
|
||||
return false;
|
||||
|
||||
if ( !CSOTVerifyObj( out_ppWritableObject ) )
|
||||
return false;
|
||||
|
||||
// If an object is in the added list, we're going to do a full write so modifying it at this point adds nothing
|
||||
// new. We'll return the current only version of it as our writable object.
|
||||
{
|
||||
const CreateOrDestroyCommitInfo_t *pInfo = InternalFindCommitInfo( pObject, m_vecObjects_Added );
|
||||
if ( pInfo )
|
||||
{
|
||||
*out_ppWritableObject = pInfo->m_pObject;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If an object is in the modified list, we've already made a copy that we can make modifications to, so we'll
|
||||
// return that copy.
|
||||
{
|
||||
const ModifyCommitInfo_t *pInfo = InternalFindCommitInfo( pObject, m_vecObjects_Modified );
|
||||
if ( pInfo )
|
||||
{
|
||||
*out_ppWritableObject = pInfo->m_pWriteableObject;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We aren't already tracking this object. We're acting as if we've never seen it before, so first make sure that
|
||||
// we're in the cache we think we're in.
|
||||
if ( !CSOTVerify( m_pLockedSOCache->FindSharedObject( *pObject ) ) )
|
||||
return false;
|
||||
|
||||
// Make a copy of our current state that we can make modifications to and track the association.
|
||||
*out_ppWritableObject = CSharedObject::Create( pObject->GetTypeID() );
|
||||
(*out_ppWritableObject)->Copy( *pObject );
|
||||
m_vecObjects_Modified.AddToTail( ModifyCommitInfo_t( *out_ppWritableObject, pObject ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSharedObjectTransactionEx::BAddNewObjectInternal( CSharedObject *pObject )
|
||||
{
|
||||
// Make sure we pass basic sanity check measures for our inputs (ie., we have inputs, we have appropriate
|
||||
// locks to manipulate those inputs, etc.).
|
||||
if ( !BIsValidInput( pObject ) )
|
||||
return false;
|
||||
|
||||
// Make sure this object isn't already in this cache. This can cause problems during rollback if we didn't
|
||||
// really add it as part of the transaction and remove it when a transaction fails.
|
||||
if ( !CSOTVerifyObj( m_pLockedSOCache->FindSharedObject( *pObject ) == NULL ) )
|
||||
return false;
|
||||
|
||||
// Make sure this object isn't already in the list of objects we're adding as part of this transaction.
|
||||
// Having the object in the list multiple times is potentially harmless, but it probably indicates some
|
||||
// calling code is doing something we don't expect.
|
||||
if ( !CSOTVerifyObj( InternalFindCommitInfo( pObject, m_vecObjects_Added ) == NULL ) )
|
||||
return false;
|
||||
|
||||
// Success.
|
||||
m_vecObjects_Added.AddToTail( CreateOrDestroyCommitInfo_t( pObject ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSharedObjectTransactionEx::BRemoveObjectInternal( CSharedObject *pObject )
|
||||
{
|
||||
// Make sure we pass basic sanity check measures for our inputs (ie., we have inputs, we have appropriate
|
||||
// locks to manipulate those inputs, etc.).
|
||||
if ( !BIsValidInput( pObject ) )
|
||||
return false;
|
||||
|
||||
// Make sure the object we're removing is in the cache we're trying to remove it from.
|
||||
if ( !CSOTVerifyObj( m_pLockedSOCache->FindSharedObject( *pObject ) ) )
|
||||
return false;
|
||||
|
||||
// Look through our lists of objects that we're adding and objects that we're modifying. If we're
|
||||
// removing an object that we're adding in the same transaction through the same pointer, this will
|
||||
// result in a broken SO cache. Removing a modified object may or may not be safe but it's probably
|
||||
// indicative of a higher-level logic bug regardless.
|
||||
if ( !CSOTVerifyObj( InternalFindCommitInfo( pObject, m_vecObjects_Added ) == NULL ) )
|
||||
return false;
|
||||
|
||||
if ( !CSOTVerifyObj( InternalFindCommitInfo( pObject, m_vecObjects_Modified ) == NULL ) )
|
||||
return false;
|
||||
|
||||
// Success.
|
||||
m_vecObjects_Removed.AddToTail( CreateOrDestroyCommitInfo_t( pObject ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
const CSharedObject *CSharedObjectTransactionEx::InternalFindSharedObject( CGCSharedObjectCache *pSOCache, const CSharedObject& soIndex ) const
|
||||
{
|
||||
FOR_EACH_VEC( m_vecObjects_Modified, i )
|
||||
{
|
||||
auto& info = m_vecObjects_Modified[i];
|
||||
if ( info.m_pObject->BIsKeyEqual( soIndex ) )
|
||||
return info.m_pObject;
|
||||
}
|
||||
|
||||
FOR_EACH_VEC( m_vecObjects_Added, i )
|
||||
{
|
||||
auto& info = m_vecObjects_Added[i];
|
||||
if ( info.m_pObject->BIsKeyEqual( soIndex ) )
|
||||
return info.m_pObject;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
const char *CSharedObjectTransactionEx::InternalPreCommit()
|
||||
{
|
||||
// ...
|
||||
if ( !BIsValidInternalState() )
|
||||
return "invalid internal state";
|
||||
|
||||
// ...
|
||||
if ( !CSOTVerify( m_pSQLAccess->BInTransaction() ) )
|
||||
return "transaction closed before commit";
|
||||
|
||||
// Did we run into some error internally building this transaction? This doesn't assert because it
|
||||
// could be a SQL error or something else that doesn't necessarily indicate a problem with the way
|
||||
// we're using this class. It *probably* indicates the calling code has a problem, but it isn't
|
||||
// guaranteed so we don't assert.
|
||||
if ( !m_bTransactionBuildSuccess )
|
||||
return "error(s) building transaction";
|
||||
|
||||
// Nothing wrong so far.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSharedObjectTransactionEx::BYieldingCommit()
|
||||
{
|
||||
const char *pszPreCommitFailureDesc = InternalPreCommit();
|
||||
if ( pszPreCommitFailureDesc )
|
||||
{
|
||||
// We can't spit out information about which cache we're dealing with here because it's possible at
|
||||
// this point that the error we're displaying is "we don't have the cache anymore!" which means pulling
|
||||
// memory is probably unsafe.
|
||||
EmitError( SPEW_GC, "Failed to commit CSharedObjectTransaction '%s': %s!\n", GetInternalTransactionDesc(), pszPreCommitFailureDesc );
|
||||
|
||||
Rollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bNetworkRelevantObjectsChanged = false;
|
||||
bool bDBRelevantObjectsChanged = false;
|
||||
bool bGeneratedCommitSQL = true;
|
||||
|
||||
// Add insert statements to SQL access instance for everything that needs to be written to the
|
||||
// database. Some types only exist in memory and have no database backing.
|
||||
FOR_EACH_VEC( m_vecObjects_Added, i )
|
||||
{
|
||||
CreateOrDestroyCommitInfo_t& info = m_vecObjects_Added[i];
|
||||
|
||||
if ( info.m_pObject->BIsDatabaseBacked() )
|
||||
{
|
||||
bGeneratedCommitSQL &= info.m_pObject->BYieldingAddInsertToTransaction( *m_pSQLAccess );
|
||||
bDBRelevantObjectsChanged |= true;
|
||||
}
|
||||
|
||||
bNetworkRelevantObjectsChanged |= info.m_pObject->BIsNetworked();
|
||||
}
|
||||
|
||||
// For every object that we've changed state on, we've also been tracking information on which
|
||||
// fields we modified. Here we ask each of the modified objects (which aren't currently in the
|
||||
// SO cache, but are clones living outside of it) to queue up their updates to the SQL transaction.
|
||||
{
|
||||
CUtlVector< int > vecDirtyFields;
|
||||
|
||||
FOR_EACH_VEC( m_vecObjects_Modified, i )
|
||||
{
|
||||
const ModifyCommitInfo_t& info = m_vecObjects_Modified[i];
|
||||
|
||||
Assert( info.m_pObject );
|
||||
Assert( info.m_pWriteableObject );
|
||||
Assert( info.m_pObject != info.m_pWriteableObject );
|
||||
|
||||
if ( info.m_pWriteableObject->BIsDatabaseBacked() )
|
||||
{
|
||||
vecDirtyFields.RemoveAll();
|
||||
m_SODirtyList.GetDirtyFieldSetByObj( info.m_pWriteableObject, vecDirtyFields );
|
||||
|
||||
bGeneratedCommitSQL &= info.m_pWriteableObject->BYieldingAddWriteToTransaction( *m_pSQLAccess, vecDirtyFields );
|
||||
bDBRelevantObjectsChanged |= true;
|
||||
}
|
||||
|
||||
bNetworkRelevantObjectsChanged |= info.m_pWriteableObject->BIsNetworked();
|
||||
|
||||
AssertMsg4( info.m_pWriteableObject->BIsDatabaseBacked() == info.m_pObject->BIsDatabaseBacked(),
|
||||
"Disagreement over DB backing state between SOs '%s' and '%s' in transaction '%s' for user '%s'!",
|
||||
info.m_pObject->GetDebugString().String(),
|
||||
info.m_pWriteableObject->GetDebugString().String(),
|
||||
GetInternalTransactionDesc(),
|
||||
m_pLockedSOCache->GetOwner().Render() );
|
||||
AssertMsg4( info.m_pWriteableObject->BIsNetworked() == info.m_pObject->BIsNetworked(),
|
||||
"Disagreement over network state between SOs '%s' and '%s' in transaction '%s' for user '%s'!",
|
||||
info.m_pObject->GetDebugString().String(),
|
||||
info.m_pWriteableObject->GetDebugString().String(),
|
||||
GetInternalTransactionDesc(),
|
||||
m_pLockedSOCache->GetOwner().Render() );
|
||||
}
|
||||
}
|
||||
|
||||
// Have each object that we'd like to remove queue up the SQL work necessary to do so.
|
||||
FOR_EACH_VEC( m_vecObjects_Removed, i )
|
||||
{
|
||||
CreateOrDestroyCommitInfo_t& info = m_vecObjects_Removed[i];
|
||||
|
||||
if ( info.m_pObject->BIsDatabaseBacked() )
|
||||
{
|
||||
bGeneratedCommitSQL &= info.m_pObject->BYieldingAddRemoveToTransaction( *m_pSQLAccess );
|
||||
bDBRelevantObjectsChanged |= true;
|
||||
}
|
||||
|
||||
// We don't have to update network state here as removes are sent immediately to the client
|
||||
// before we've even flushed our dirty updates.
|
||||
}
|
||||
|
||||
// Our "did we generate the SQL to do this work in the DB" variable starts off true, so the only
|
||||
// way we'd expect it to be false here is if we attempted to do work above and ran into some errors.
|
||||
// If we don't have any SQL work to do at all, for example if we're only adding/modifying/removing
|
||||
// memory-only items, then "bGeneratedCommitSQL" will be true and "bDBRelevantObjectsChanged" will
|
||||
// be false.
|
||||
if ( !bGeneratedCommitSQL )
|
||||
{
|
||||
EmitError( SPEW_GC, "Failed to commit CSharedObjectTransaction '%s' for user '%s': failed to add inserts/writes.\n", GetInternalTransactionDesc(), m_pLockedSOCache->GetOwner().Render() );
|
||||
|
||||
Rollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to commit DB transaction. We don't know for sure whether we're the only code that's adding commands
|
||||
// to this transaction, so we can't completely skip this if we didn't do any SQL work internally. We can
|
||||
// say "did we do anything internally?; if not, maybe we'll be empty".
|
||||
if ( !m_pSQLAccess->BCommitTransaction( !bDBRelevantObjectsChanged ) )
|
||||
{
|
||||
EmitError( SPEW_GC, "Failed to commit CSharedObjectTransaction '%s' for user '%s': SQL transaction failure.\n", GetInternalTransactionDesc(), m_pLockedSOCache->GetOwner().Render() );
|
||||
|
||||
Rollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
// The database work committed successfully, so we update our memory state to match the state we just wrote
|
||||
// to the DB.
|
||||
FOR_EACH_VEC( m_vecObjects_Added, i )
|
||||
{
|
||||
CreateOrDestroyCommitInfo_t& info = m_vecObjects_Added[i];
|
||||
m_pLockedSOCache->AddObject( info.m_pObject ); // internally will assert if cache isn't locked
|
||||
}
|
||||
|
||||
FOR_EACH_VEC( m_vecObjects_Modified, i )
|
||||
{
|
||||
ModifyCommitInfo_t& info = m_vecObjects_Modified[i];
|
||||
info.m_pObject->Copy( *info.m_pWriteableObject ); // stomp the version that's already in the cache with the properties from where we've been writing
|
||||
delete info.m_pWriteableObject;
|
||||
}
|
||||
|
||||
FOR_EACH_VEC( m_vecObjects_Removed, i )
|
||||
{
|
||||
CreateOrDestroyCommitInfo_t &info = m_vecObjects_Removed[i];
|
||||
Verify( m_pLockedSOCache->BDestroyObject( *info.m_pObject, false ) ); // internally will assert if cache isn't locked
|
||||
}
|
||||
|
||||
// Did we change anything that affected network state? If so, tell our cache to flush the dirty
|
||||
// state list.
|
||||
if ( bNetworkRelevantObjectsChanged )
|
||||
{
|
||||
// Our cache is responsible for sending all the network updates, including creates from the
|
||||
// AddObject() calls above, so now that our commit has succeeded we copy over our list of
|
||||
// dirty objects from inside this transaction.
|
||||
for ( const ModifyCommitInfo_t& info : m_vecObjects_Modified )
|
||||
{
|
||||
m_pLockedSOCache->DirtyNetworkObject( info.m_pObject );
|
||||
}
|
||||
|
||||
m_pLockedSOCache->SendAllNetworkUpdates();
|
||||
}
|
||||
|
||||
// Cleanup.
|
||||
m_vecObjects_Added.RemoveAll();
|
||||
m_vecObjects_Modified.RemoveAll();
|
||||
m_vecObjects_Removed.RemoveAll();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CSharedObjectTransactionEx::Rollback()
|
||||
{
|
||||
// Clean up any memory allocated to handle any database work that is outstanding but not yet
|
||||
// committed.
|
||||
m_pSQLAccess->RollbackTransaction();
|
||||
Assert( !m_pSQLAccess->BInTransaction() );
|
||||
|
||||
// Clean up any in-memory changes that are currently outstanding.
|
||||
|
||||
// We made new objects for our adds, so we need to free up that memory.
|
||||
FOR_EACH_VEC( m_vecObjects_Added, i )
|
||||
{
|
||||
delete m_vecObjects_Added[i].m_pObject;
|
||||
}
|
||||
m_vecObjects_Added.RemoveAll();
|
||||
|
||||
// For our modifies, we haven't done any work on the versions that are in the cache, so all we have
|
||||
// to do is delete the temp memory that we allocated for a writable version.
|
||||
FOR_EACH_VEC( m_vecObjects_Modified, i )
|
||||
{
|
||||
ModifyCommitInfo_t& info = m_vecObjects_Modified[i];
|
||||
delete info.m_pWriteableObject;
|
||||
}
|
||||
m_vecObjects_Modified.RemoveAll();
|
||||
|
||||
// We didn't actually do any in-memory work for our items that were set to be deleted, so we
|
||||
// can just free the memory for our tracking state.
|
||||
m_vecObjects_Removed.RemoveAll();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
CSharedObjectTransaction::CSharedObjectTransaction( CSQLAccess &sqlAccess, const char *pName )
|
||||
: m_sqlAccess( sqlAccess )
|
||||
{
|
||||
if ( m_sqlAccess.BInTransaction() == false )
|
||||
{
|
||||
m_sqlAccess.BBeginTransaction( pName );
|
||||
}
|
||||
}
|
||||
|
||||
CSharedObjectTransaction::~CSharedObjectTransaction()
|
||||
{
|
||||
Rollback();
|
||||
}
|
||||
|
||||
CSharedObjectTransaction::undoinfo_t *CSharedObjectTransaction::FindObjectInVector( const CSharedObject *pObject, CUtlVector<undoinfo_t> &vec ) const
|
||||
{
|
||||
FOR_EACH_VEC( vec, i )
|
||||
{
|
||||
if ( vec[i].pObject == pObject )
|
||||
return &vec[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool CSharedObjectTransaction::AssertValidInput( const CGCSharedObjectCache *pSOCache, const CSharedObject *pObject, const char *pszContext )
|
||||
{
|
||||
Assert( pszContext );
|
||||
|
||||
Assert( pSOCache );
|
||||
Assert( pObject );
|
||||
if ( pSOCache == NULL || pObject == NULL )
|
||||
{
|
||||
SetError( CFmtStr( "%s: attempt to manipulate invalid SO cache %s, object %s", pszContext, pSOCache ? pSOCache->GetOwner().Render() : "[none]", pObject ? pObject->GetDebugString().String() : "[none]" ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool bSOCachedLocked = GGCBase()->IsSteamIDLockedByCurJob( pSOCache->GetOwner() );
|
||||
Assert( bSOCachedLocked );
|
||||
if ( !bSOCachedLocked )
|
||||
{
|
||||
SetError( CFmtStr( "%s: attempt to manipulate non-locked SO cache %s to add object %s", pszContext, pSOCache->GetOwner().Render(), pObject->GetDebugString().String() ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
return pSOCache != NULL && pObject != NULL && bSOCachedLocked;
|
||||
}
|
||||
|
||||
void CSharedObjectTransaction::AddManagedObject( CGCSharedObjectCache *pSOCache, CSharedObject *pObject )
|
||||
{
|
||||
if ( !AssertValidInput( pSOCache, pObject, "AddManagedObject()" ) )
|
||||
return;
|
||||
|
||||
// if an object is in the added list, we're going to do a full write so modifying it at this point adds nothing
|
||||
// new; if an object is in the modified list, we already tracked the initial state; either way we have no new
|
||||
// data to track here
|
||||
if ( FindObjectInVector( pObject, m_vecObjects_Added ) || FindObjectInVector( pObject, m_vecObjects_Modified ) )
|
||||
return;
|
||||
|
||||
undoinfo_t info = { pObject, pSOCache, NULL };
|
||||
info.pOriginalCopy = CSharedObject::Create( pObject->GetTypeID() );
|
||||
info.pOriginalCopy->Copy( *pObject );
|
||||
m_vecObjects_Modified.AddToTail( info );
|
||||
}
|
||||
|
||||
void CSharedObjectTransaction::AddNewObject( CGCSharedObjectCache *pSOCache, CSharedObject *pObject )
|
||||
{
|
||||
if ( !AssertValidInput( pSOCache, pObject, "AddNewObject()" ) )
|
||||
return;
|
||||
|
||||
if ( FindObjectInVector( pObject, m_vecObjects_Added ) )
|
||||
return;
|
||||
|
||||
undoinfo_t info = { pObject, pSOCache, NULL };
|
||||
m_vecObjects_Added.AddToTail( info );
|
||||
}
|
||||
|
||||
void CSharedObjectTransaction::RemoveObject( CGCSharedObjectCache *pSOCache, CSharedObject *pObject )
|
||||
{
|
||||
if ( !AssertValidInput( pSOCache, pObject, "RemoveObject()" ) )
|
||||
return;
|
||||
|
||||
// make sure the object we're removing is in the cache we're trying to remove it from.
|
||||
AssertMsg1( pSOCache->FindSharedObject( *pObject ), "Attempting to remove object '%s' from non-owning cache!", pObject->GetDebugString().Get() );
|
||||
|
||||
// look through our lists of objects that we're adding and objects that we're modifying. If we're
|
||||
// removing an object that we're adding in the same transaction through the same pointer, this will
|
||||
// result in a broken SO cache. Removing a modified object may or may not be safe but it's probably
|
||||
// indicative of a higher-level logic bug regardless.
|
||||
if ( undoinfo_t *pInfo = FindObjectInVector( pObject, m_vecObjects_Added ) )
|
||||
{
|
||||
EmitError( SPEW_GC, "Attempting to add and remove the same object from the same CSharedObjectTransaction! Object: %s", pInfo->pObject->GetDebugString().Get() );
|
||||
Assert( !"Attempting to add and remove the same object from the same CSharedObjectTransaction!" );
|
||||
m_vecObjects_Added.FindAndFastRemove( *pInfo );
|
||||
}
|
||||
if ( undoinfo_t *pInfo = FindObjectInVector( pObject, m_vecObjects_Modified ) )
|
||||
{
|
||||
EmitError( SPEW_GC, "Attempting to modify and remove the same object from the same CSharedObjectTransaction! Object: %s", pInfo->pObject->GetDebugString().Get() );
|
||||
Assert( !"Attempting to modify and remove the same object from the same CSharedObjectTransaction!" );
|
||||
m_vecObjects_Modified.FindAndFastRemove( *pInfo );
|
||||
}
|
||||
|
||||
// @note Tom Bui: the act of removing an item may change the object, so to roll that back,
|
||||
// we need the original version
|
||||
undoinfo_t info = { pObject, pSOCache, NULL };
|
||||
info.pOriginalCopy = CSharedObject::Create( pObject->GetTypeID() );
|
||||
info.pOriginalCopy->Copy( *pObject );
|
||||
m_vecObjects_Removed.AddToTail( info );
|
||||
|
||||
if ( !pObject->BYieldingAddRemoveToTransaction( m_sqlAccess ) )
|
||||
{
|
||||
SetError( "RemoveObject(): BYieldingAddRemoveToTransaction() failed" );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void CSharedObjectTransaction::ModifiedObject( CGCSharedObjectCache *pSOCache, CSharedObject *pObject, uint32 unFieldIdx )
|
||||
{
|
||||
if ( !AssertValidInput( pSOCache, pObject, "ModifiedObject()" ) )
|
||||
return;
|
||||
|
||||
// look for an object in the transaction -- this might be a new object created for this
|
||||
// transaction or it might be an object we've already tagged for modification; we don't
|
||||
// use FindSharedObject() for this because we might not be in an SO cache yet
|
||||
|
||||
// if we're in the add list, we aren't intended to be in a cache yet, and so we also don't
|
||||
// have to dirty any fields -- we don't exist for real yet so when we finalize this transaction,
|
||||
// effectively *everything* is dirty
|
||||
if ( FindObjectInVector( pObject, m_vecObjects_Added ) )
|
||||
return;
|
||||
|
||||
// make sure the object we're removing is in the cache we think it is. This check has to happen
|
||||
// after the "is in the added list?" check above because we won't actually put items in the cache
|
||||
// for real until after the SQL transaction succeeds
|
||||
AssertMsg1( pSOCache->FindSharedObject( *pObject ), "Attempting to modify object '%s' in non-owning cache!", pObject->GetDebugString().Get() );
|
||||
|
||||
undoinfo_t *pInfo = FindObjectInVector( pObject, m_vecObjects_Modified );
|
||||
if ( pInfo )
|
||||
{
|
||||
pInfo->pSOCache->DirtyObjectField( pObject, unFieldIdx );
|
||||
return;
|
||||
}
|
||||
|
||||
Assert( !"Attempt to modify an unmanaged object in CSharedObjectTransaction!" );
|
||||
SetError( CFmtStr( "ModifiedObject(): attempt to modify an unmanaged object %s", pObject->GetDebugString().String() ) );
|
||||
}
|
||||
|
||||
CSharedObject *CSharedObjectTransaction::FindSharedObject( CGCSharedObjectCache *pSOCache, const CSharedObject &soIndex )
|
||||
{
|
||||
// search in modified objects
|
||||
FOR_EACH_VEC( m_vecObjects_Modified, i )
|
||||
{
|
||||
undoinfo_t &info = m_vecObjects_Modified[i];
|
||||
if ( info.pSOCache == pSOCache && info.pObject->BIsKeyEqual( soIndex ) )
|
||||
return info.pObject;
|
||||
}
|
||||
|
||||
// search in new objects
|
||||
FOR_EACH_VEC( m_vecObjects_Added, i )
|
||||
{
|
||||
undoinfo_t &info = m_vecObjects_Added[i];
|
||||
if ( info.pSOCache == pSOCache && info.pObject->BIsKeyEqual( soIndex ) )
|
||||
return info.pObject;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void CSharedObjectTransaction::Rollback()
|
||||
{
|
||||
if ( m_sqlAccess.BInTransaction() )
|
||||
{
|
||||
m_sqlAccess.RollbackTransaction();
|
||||
}
|
||||
Undo();
|
||||
}
|
||||
|
||||
bool CSharedObjectTransaction::BYieldingCommit( bool bAllowEmpty )
|
||||
{
|
||||
const char *pszPreExistingError = GetError();
|
||||
if ( pszPreExistingError )
|
||||
{
|
||||
EmitError( SPEW_GC, "Failed to commit CSharedObjectTransaction '%s': %s.\n", PchName(), pszPreExistingError );
|
||||
|
||||
Undo();
|
||||
return false;
|
||||
}
|
||||
|
||||
Assert( m_sqlAccess.BInTransaction() );
|
||||
if ( !m_sqlAccess.BInTransaction() )
|
||||
{
|
||||
EmitError( SPEW_GC, "Failed to commit CSharedObjectTransaction '%s': transaction closed before commit!\n", PchName() );
|
||||
|
||||
Undo();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bSuccess = true;
|
||||
bool bDBRelevantObjectsChanged = false;
|
||||
|
||||
// add insert statements to sql access
|
||||
FOR_EACH_VEC( m_vecObjects_Added, i )
|
||||
{
|
||||
undoinfo_t &info = m_vecObjects_Added[i];
|
||||
if ( info.pObject->BIsDatabaseBacked() )
|
||||
{
|
||||
bSuccess &= info.pObject->BYieldingAddInsertToTransaction( m_sqlAccess );
|
||||
bDBRelevantObjectsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// add update statements to sql access
|
||||
FOR_EACH_VEC( m_vecObjects_Modified, i )
|
||||
{
|
||||
undoinfo_t &info = m_vecObjects_Modified[i];
|
||||
if ( info.pObject->BIsDatabaseBacked() )
|
||||
{
|
||||
bSuccess &= info.pSOCache->BYieldingAddWriteToTransaction( info.pObject, m_sqlAccess );
|
||||
bDBRelevantObjectsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( bSuccess == false )
|
||||
{
|
||||
EmitError( SPEW_GC, "Failed to commit CSharedObjectTransaction '%s': failed to add inserts/writes.\n", PchName() );
|
||||
|
||||
Undo();
|
||||
return false;
|
||||
}
|
||||
|
||||
// try to commit db transaction.
|
||||
if ( m_sqlAccess.BCommitTransaction( bAllowEmpty && !bDBRelevantObjectsChanged ) == false )
|
||||
{
|
||||
EmitError( SPEW_GC, "Failed to commit CSharedObjectTransaction '%s': SQL transaction failure.\n", PchName() );
|
||||
|
||||
Undo();
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove objects from SO cache
|
||||
FOR_EACH_VEC( m_vecObjects_Removed, i )
|
||||
{
|
||||
undoinfo_t &info = m_vecObjects_Removed[i];
|
||||
DbgVerify( info.pSOCache->BDestroyObject( *info.pObject, false ) ); // internally will assert if cache isn't locked
|
||||
delete info.pOriginalCopy;
|
||||
}
|
||||
m_vecObjects_Removed.RemoveAll();
|
||||
|
||||
// add new objects to SO cache
|
||||
FOR_EACH_VEC( m_vecObjects_Added, i )
|
||||
{
|
||||
undoinfo_t &info = m_vecObjects_Added[i];
|
||||
info.pSOCache->AddObject( info.pObject ); // internally will assert if cache isn't locked
|
||||
Assert( info.pOriginalCopy == NULL );
|
||||
}
|
||||
|
||||
// free up memory for original state of modified objects
|
||||
FOR_EACH_VEC( m_vecObjects_Modified, i )
|
||||
{
|
||||
undoinfo_t &info = m_vecObjects_Modified[i];
|
||||
info.pSOCache->DirtyNetworkObject( info.pObject );
|
||||
delete info.pOriginalCopy;
|
||||
}
|
||||
|
||||
// send network updates for objects that were added or modified
|
||||
// this is OK to call more than once on a CGCSharedObjectCache, because internally
|
||||
// it keeps a list of things that were marked dirty
|
||||
FOR_EACH_VEC( m_vecObjects_Added, i )
|
||||
{
|
||||
m_vecObjects_Added[i].pSOCache->SendAllNetworkUpdates();
|
||||
}
|
||||
FOR_EACH_VEC( m_vecObjects_Modified, i )
|
||||
{
|
||||
m_vecObjects_Modified[i].pSOCache->SendAllNetworkUpdates();
|
||||
}
|
||||
|
||||
m_vecObjects_Added.RemoveAll();
|
||||
m_vecObjects_Modified.RemoveAll();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CSharedObjectTransaction::Undo()
|
||||
{
|
||||
FOR_EACH_VEC( m_vecObjects_Added, i )
|
||||
{
|
||||
undoinfo_t &info = m_vecObjects_Added[i];
|
||||
delete info.pObject;
|
||||
}
|
||||
m_vecObjects_Added.RemoveAll();
|
||||
|
||||
FOR_EACH_VEC( m_vecObjects_Removed, i )
|
||||
{
|
||||
undoinfo_t &info = m_vecObjects_Removed[i];
|
||||
AssertMsg1( GGCBase()->IsSteamIDLockedByCurJob( info.pSOCache->GetOwner() ), "Attempt to modify in-memory object '%s' during CSharedObjectTransaction removal rollback.", info.pObject->GetDebugString().Get() );
|
||||
info.pObject->Copy( *info.pOriginalCopy );
|
||||
delete info.pOriginalCopy;
|
||||
}
|
||||
m_vecObjects_Removed.RemoveAll();
|
||||
|
||||
FOR_EACH_VEC( m_vecObjects_Modified, i )
|
||||
{
|
||||
undoinfo_t &info = m_vecObjects_Modified[i];
|
||||
AssertMsg1( GGCBase()->IsSteamIDLockedByCurJob( info.pSOCache->GetOwner() ), "Attempt to modify in-memory object '%s' during CSharedObjectTransaction modify rollback.", info.pObject->GetDebugString().Get() );
|
||||
info.pObject->Copy( *info.pOriginalCopy );
|
||||
delete info.pOriginalCopy;
|
||||
}
|
||||
m_vecObjects_Modified.RemoveAll();
|
||||
|
||||
ClearError();
|
||||
}
|
||||
|
||||
const char *CSharedObjectTransaction::PchName() const
|
||||
{
|
||||
return m_sqlAccess.PchTransactionName();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,368 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Sets of columns in SQL queries
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructs a column set with no columns in it
|
||||
//-----------------------------------------------------------------------------
|
||||
CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo )
|
||||
: m_pRecordInfo( pRecordInfo )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructs a column set with a single column in it
|
||||
// Inputs: nColumn - the column to add
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo, int col1 )
|
||||
: m_pRecordInfo( pRecordInfo )
|
||||
{
|
||||
m_vecColumns.AddToTail( col1 );
|
||||
}
|
||||
|
||||
CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2 )
|
||||
: m_pRecordInfo( pRecordInfo )
|
||||
{
|
||||
m_vecColumns.EnsureCapacity( 2 );
|
||||
m_vecColumns.AddToTail( col1 );
|
||||
m_vecColumns.AddToTail( col2 );
|
||||
}
|
||||
|
||||
CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3 )
|
||||
: m_pRecordInfo( pRecordInfo )
|
||||
{
|
||||
m_vecColumns.EnsureCapacity( 3 );
|
||||
m_vecColumns.AddToTail( col1 );
|
||||
m_vecColumns.AddToTail( col2 );
|
||||
m_vecColumns.AddToTail( col3 );
|
||||
}
|
||||
|
||||
CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3, int col4 )
|
||||
: m_pRecordInfo( pRecordInfo )
|
||||
{
|
||||
m_vecColumns.EnsureCapacity( 4 );
|
||||
m_vecColumns.AddToTail( col1 );
|
||||
m_vecColumns.AddToTail( col2 );
|
||||
m_vecColumns.AddToTail( col3 );
|
||||
m_vecColumns.AddToTail( col4 );
|
||||
}
|
||||
|
||||
CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3, int col4, int col5 )
|
||||
: m_pRecordInfo( pRecordInfo )
|
||||
{
|
||||
m_vecColumns.EnsureCapacity( 5 );
|
||||
m_vecColumns.AddToTail( col1 );
|
||||
m_vecColumns.AddToTail( col2 );
|
||||
m_vecColumns.AddToTail( col3 );
|
||||
m_vecColumns.AddToTail( col4 );
|
||||
m_vecColumns.AddToTail( col5 );
|
||||
}
|
||||
|
||||
CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3, int col4, int col5, int col6 )
|
||||
: m_pRecordInfo( pRecordInfo )
|
||||
{
|
||||
m_vecColumns.EnsureCapacity( 6 );
|
||||
m_vecColumns.AddToTail( col1 );
|
||||
m_vecColumns.AddToTail( col2 );
|
||||
m_vecColumns.AddToTail( col3 );
|
||||
m_vecColumns.AddToTail( col4 );
|
||||
m_vecColumns.AddToTail( col5 );
|
||||
m_vecColumns.AddToTail( col6 );
|
||||
}
|
||||
|
||||
CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3, int col4, int col5, int col6, int col7 )
|
||||
: m_pRecordInfo( pRecordInfo )
|
||||
{
|
||||
m_vecColumns.EnsureCapacity( 7 );
|
||||
m_vecColumns.AddToTail( col1 );
|
||||
m_vecColumns.AddToTail( col2 );
|
||||
m_vecColumns.AddToTail( col3 );
|
||||
m_vecColumns.AddToTail( col4 );
|
||||
m_vecColumns.AddToTail( col5 );
|
||||
m_vecColumns.AddToTail( col6 );
|
||||
m_vecColumns.AddToTail( col7 );
|
||||
}
|
||||
|
||||
CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3, int col4, int col5, int col6, int col7, int col8 )
|
||||
: m_pRecordInfo( pRecordInfo )
|
||||
{
|
||||
m_vecColumns.EnsureCapacity( 8 );
|
||||
m_vecColumns.AddToTail( col1 );
|
||||
m_vecColumns.AddToTail( col2 );
|
||||
m_vecColumns.AddToTail( col3 );
|
||||
m_vecColumns.AddToTail( col4 );
|
||||
m_vecColumns.AddToTail( col5 );
|
||||
m_vecColumns.AddToTail( col6 );
|
||||
m_vecColumns.AddToTail( col7 );
|
||||
m_vecColumns.AddToTail( col8 );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Copy constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CColumnSet::CColumnSet( const CColumnSet & rhs )
|
||||
{
|
||||
MEM_ALLOC_CREDIT_("CColumnSet");
|
||||
m_vecColumns.CopyArray( rhs.m_vecColumns.Base(), rhs.m_vecColumns.Count() );
|
||||
m_pRecordInfo = rhs.m_pRecordInfo;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Assignment operator
|
||||
//-----------------------------------------------------------------------------
|
||||
CColumnSet & CColumnSet::operator=( const CColumnSet & rhs )
|
||||
{
|
||||
MEM_ALLOC_CREDIT_("CColumnSet");
|
||||
m_vecColumns.CopyArray( rhs.m_vecColumns.Base(), rhs.m_vecColumns.Count() );
|
||||
m_pRecordInfo = rhs.m_pRecordInfo;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Addition operator. lhs ColumnSet will be a union of the two
|
||||
// ColumnSets
|
||||
//-----------------------------------------------------------------------------
|
||||
CColumnSet & CColumnSet::operator+=( const CColumnSet & rhs )
|
||||
{
|
||||
Assert( this->GetRecordInfo() == rhs.GetRecordInfo() );
|
||||
FOR_EACH_COLUMN_IN_SET( rhs, i )
|
||||
{
|
||||
BAddColumn( rhs.GetColumn( i ) );
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Addition operator. Returns a union of lhs and rhs
|
||||
//-----------------------------------------------------------------------------
|
||||
const CColumnSet CColumnSet::operator+( const CColumnSet & rhs ) const
|
||||
{
|
||||
return CColumnSet( *this ) += rhs;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Adds a column to the set if it is
|
||||
// Inputs: nColumn - THe column to add
|
||||
//-----------------------------------------------------------------------------
|
||||
void CColumnSet::BAddColumn( int nColumn )
|
||||
{
|
||||
if( nColumn >= 0 && nColumn < m_pRecordInfo->GetNumColumns() )
|
||||
{
|
||||
//not sure best way to handle the 'is already set case'
|
||||
if( !IsSet( nColumn ) )
|
||||
m_vecColumns.AddToTail( nColumn );
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertMsg3( false, "Attempting to set an out of range column on schema type %s, %d (of %d)", GetRecordInfo()->GetName(), nColumn, m_pRecordInfo->GetNumColumns() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Removes a column from the set
|
||||
// Inputs: nColumn - THe column to remove
|
||||
//-----------------------------------------------------------------------------
|
||||
void CColumnSet::BRemoveColumn( int nColumn )
|
||||
{
|
||||
m_vecColumns.FindAndRemove( nColumn );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns true if a column is in the set
|
||||
// Inputs: nColumn - THe column to test
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CColumnSet::IsSet( int nColumn ) const
|
||||
{
|
||||
int nIndex = m_vecColumns.Find( nColumn );
|
||||
return m_vecColumns.IsValidIndex( nIndex );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns the number of columns in the set
|
||||
//-----------------------------------------------------------------------------
|
||||
uint32 CColumnSet::GetColumnCount() const
|
||||
{
|
||||
return m_vecColumns.Count();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns the column index of the Nth column in the set
|
||||
// Inputs: nIndex - the position in the set to return a column index for.
|
||||
//-----------------------------------------------------------------------------
|
||||
int CColumnSet::GetColumn( int nIndex ) const
|
||||
{
|
||||
return m_vecColumns[nIndex];
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns a CColumnInfo object for the nth column in the set
|
||||
// Inputs: nIndex - the position in the set to return a column info for.
|
||||
//-----------------------------------------------------------------------------
|
||||
const CColumnInfo & CColumnSet::GetColumnInfo( int nIndex ) const
|
||||
{
|
||||
return m_pRecordInfo->GetColumnInfo( GetColumn( nIndex ) );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Empties the column set
|
||||
//-----------------------------------------------------------------------------
|
||||
void CColumnSet::MakeEmpty()
|
||||
{
|
||||
m_vecColumns.RemoveAll();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Makes the column set be the full set of all columns in the record info
|
||||
//-----------------------------------------------------------------------------
|
||||
void CColumnSet::MakeFull()
|
||||
{
|
||||
MakeEmpty();
|
||||
const int nNumColumns = m_pRecordInfo->GetNumColumns();
|
||||
m_vecColumns.EnsureCapacity( nNumColumns );
|
||||
for( int nColumn = 0; nColumn < m_pRecordInfo->GetNumColumns(); nColumn++ )
|
||||
{
|
||||
//do a direct add to avoid the exponential cost since we know we won't have conflicts
|
||||
m_vecColumns.AddToTail( nColumn );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Makes the column set be the full set of all insertable columns in
|
||||
// the record info
|
||||
//-----------------------------------------------------------------------------
|
||||
void CColumnSet::MakeInsertable()
|
||||
{
|
||||
MakeEmpty();
|
||||
for( int nColumn = 0; nColumn < m_pRecordInfo->GetNumColumns(); nColumn++ )
|
||||
{
|
||||
const CColumnInfo & columnInfo = m_pRecordInfo->GetColumnInfo( nColumn );
|
||||
if( columnInfo.BIsInsertable() )
|
||||
{
|
||||
//do a direct add to avoid the exponential cost since we know we won't have conflicts
|
||||
m_vecColumns.AddToTail( nColumn );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Makes the column set be the full set of all noninsertable columns in
|
||||
// the record info
|
||||
//-----------------------------------------------------------------------------
|
||||
void CColumnSet::MakeNoninsertable()
|
||||
{
|
||||
MakeEmpty();
|
||||
for( int nColumn = 0; nColumn < m_pRecordInfo->GetNumColumns(); nColumn++ )
|
||||
{
|
||||
const CColumnInfo & columnInfo = m_pRecordInfo->GetColumnInfo( nColumn );
|
||||
if( !columnInfo.BIsInsertable() )
|
||||
{
|
||||
//do a direct add to avoid the exponential cost since we know we won't have conflicts
|
||||
m_vecColumns.AddToTail( nColumn );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Makes the column set be the full set of all primary key columns in
|
||||
// the record info
|
||||
//-----------------------------------------------------------------------------
|
||||
void CColumnSet::MakePrimaryKey()
|
||||
{
|
||||
MakeEmpty();
|
||||
for( int nColumn = 0; nColumn < m_pRecordInfo->GetNumColumns(); nColumn++ )
|
||||
{
|
||||
const CColumnInfo & columnInfo = m_pRecordInfo->GetColumnInfo( nColumn );
|
||||
if( columnInfo.BIsPrimaryKey() )
|
||||
{
|
||||
//do a direct add to avoid the exponential cost since we know we won't have conflicts
|
||||
m_vecColumns.AddToTail( nColumn );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Makes the column set be the full set of all primary key columns in
|
||||
// the record info
|
||||
//-----------------------------------------------------------------------------
|
||||
void CColumnSet::MakeInverse( const CColumnSet & columnSet )
|
||||
{
|
||||
MakeEmpty();
|
||||
for( int nColumn = 0; nColumn < m_pRecordInfo->GetNumColumns(); nColumn++ )
|
||||
{
|
||||
if( !columnSet.IsSet( nColumn ) )
|
||||
{
|
||||
//do a direct add to avoid the exponential cost since we know we won't have conflicts
|
||||
m_vecColumns.AddToTail( nColumn );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// determines if the current column set has all fields set. Useful for detection of new columns being added to the schema
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CColumnSet::BAreAllFieldsSet() const
|
||||
{
|
||||
for( int nColumn = 0; nColumn < m_pRecordInfo->GetNumColumns(); nColumn++ )
|
||||
{
|
||||
if( !IsSet( nColumn ) )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns a Column Set which is the inverse of the given column set
|
||||
// STATIC - Difference from MakeInverse is that it has a return value
|
||||
//-----------------------------------------------------------------------------
|
||||
CColumnSet CColumnSet::Inverse( const CColumnSet & columnSet )
|
||||
{
|
||||
CColumnSet set( columnSet.GetRecordInfo() );
|
||||
set.MakeInverse( columnSet );
|
||||
return set;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Claims the memory for CColumnSet
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
void CColumnSet::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
// these are INSIDE the function instead of outside so the interface
|
||||
// doesn't change
|
||||
VALIDATE_SCOPE();
|
||||
|
||||
ValidateObj( m_vecColumns );
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
} // namespace GCSDK
|
||||
@@ -0,0 +1,856 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CRecordBase::~CRecordBase()
|
||||
{
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Copy constructor
|
||||
// Input: that - CRecord to copy from
|
||||
//-----------------------------------------------------------------------------
|
||||
CRecordBase::CRecordBase( const CRecordBase &that )
|
||||
{
|
||||
*this = that;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Assignment operator - COPIES the record data
|
||||
// Input: that - CRecord to copy from
|
||||
//-----------------------------------------------------------------------------
|
||||
CRecordBase& CRecordBase::operator = ( const CRecordBase & that )
|
||||
{
|
||||
Assert( GetITable() == that.GetITable() );
|
||||
|
||||
// COPY that record
|
||||
Copy( that );
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Copies the data in the record. This is overridden by CRecordVar and
|
||||
// CRecordExternal
|
||||
// Input: that - CRecord to copy from
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordBase::Copy( const CRecordBase & that )
|
||||
{
|
||||
Cleanup();
|
||||
Q_memcpy( PubRecordFixed(), that.PubRecordFixed(), GetPSchema()->CubRecordFixed() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Return the record info for this record's schema
|
||||
//-----------------------------------------------------------------------------
|
||||
const CRecordInfo *CRecordBase::GetPRecordInfo() const
|
||||
{
|
||||
return GetPSchema()->GetRecordInfo();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Copies the data in the var record.
|
||||
// Input: that - CRecord to copy from
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordVar::Copy( const CRecordBase & baseThat )
|
||||
{
|
||||
const CRecordVar & that = (const CRecordVar &)baseThat;
|
||||
|
||||
// COPY that record
|
||||
Cleanup();
|
||||
m_pSchema = that.m_pSchema;
|
||||
Q_memcpy( PubRecordFixed(), that.PubRecordFixed(), GetPSchema()->CubRecordFixed() );
|
||||
|
||||
SetFlag( k_EAllocatedVarBlock, false );
|
||||
if ( VarFieldBlockInfo_t *pVarBlockInfo = GetPSchema()->PVarFieldBlockInfoFromRecord( PubRecordFixed() ) )
|
||||
{
|
||||
if ( pVarBlockInfo->m_cubBlock )
|
||||
{
|
||||
void *pvNewBlock = malloc( pVarBlockInfo->m_cubBlock );
|
||||
Q_memcpy( pvNewBlock, pVarBlockInfo->m_pubBlock, pVarBlockInfo->m_cubBlock );
|
||||
pVarBlockInfo->m_pubBlock = ( uint8 * )pvNewBlock;
|
||||
SetFlag( k_EAllocatedVarBlock, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Copies the data in the var record.
|
||||
// Input: that - CRecord to copy from
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordExternal::Copy( const CRecordBase & baseThat )
|
||||
{
|
||||
const CRecordExternal & that = (const CRecordExternal &)baseThat;
|
||||
|
||||
Cleanup();
|
||||
m_pSchema = that.m_pSchema;
|
||||
|
||||
m_pubRecordFixedExternal = ( uint8 * )malloc( m_pSchema->CubRecordFixed() );
|
||||
Q_memcpy( m_pubRecordFixedExternal, that.PubRecordFixed(), m_pSchema->CubRecordFixed() );
|
||||
SetFlag( k_EAllocatedFixed, true );
|
||||
|
||||
SetFlag( k_EAllocatedVarBlock, false );
|
||||
if ( VarFieldBlockInfo_t *pVarBlockInfo = m_pSchema->PVarFieldBlockInfoFromRecord( PubRecordFixed() ) )
|
||||
{
|
||||
if ( pVarBlockInfo->m_cubBlock )
|
||||
{
|
||||
void *pvNewBlock = malloc( pVarBlockInfo->m_cubBlock );
|
||||
Q_memcpy( pvNewBlock, pVarBlockInfo->m_pubBlock, pVarBlockInfo->m_cubBlock );
|
||||
pVarBlockInfo->m_pubBlock = ( uint8 * )pvNewBlock;
|
||||
SetFlag( k_EAllocatedVarBlock, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Initialize to an empty record
|
||||
// Input: pSchema - Schema for the record this will hold
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordExternal::Init( CSchema *pSchema )
|
||||
{
|
||||
Cleanup();
|
||||
m_pSchema = pSchema;
|
||||
m_pubRecordFixedExternal = ( uint8 * )malloc( m_pSchema->CubRecordFixed() );
|
||||
Q_memset( m_pubRecordFixedExternal, 0, m_pSchema->CubRecordFixed() );
|
||||
SetFlag( k_EAllocatedFixed, true );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Initialize pointing to a record expanded in memory
|
||||
// Input: pSchema - Schema for the record this will hold
|
||||
// pubRecord - Pointer to fixed record data
|
||||
// bTakeOwnership - Should we delete the record when destroyed
|
||||
// Output: Size of the record's data
|
||||
//-----------------------------------------------------------------------------
|
||||
int CRecordBase::InitFromBytes( uint8 *pubRecord )
|
||||
{
|
||||
Cleanup();
|
||||
|
||||
Q_memcpy( PubRecordFixed(), pubRecord, GetPSchema()->CubRecordFixed() );
|
||||
int cubRead = GetPSchema()->CubRecordFixed();
|
||||
return cubRead;
|
||||
}
|
||||
|
||||
int CRecordVar::InitFromBytes( uint8 *pubRecord )
|
||||
{
|
||||
Cleanup();
|
||||
|
||||
Q_memcpy( PubRecordFixed(), pubRecord, GetPSchema()->CubRecordFixed() );
|
||||
int cubRead = GetPSchema()->CubRecordFixed();
|
||||
if ( VarFieldBlockInfo_t *pVarBlockInfo = GetPSchema()->PVarFieldBlockInfoFromRecord( PubRecordFixed() ) )
|
||||
{
|
||||
if ( pVarBlockInfo->m_cubBlock )
|
||||
{
|
||||
void *pvNewBlock = malloc( pVarBlockInfo->m_cubBlock );
|
||||
Q_memcpy( pvNewBlock, pVarBlockInfo->m_pubBlock, pVarBlockInfo->m_cubBlock );
|
||||
pVarBlockInfo->m_pubBlock = ( uint8 * )pvNewBlock;
|
||||
SetFlag( k_EAllocatedVarBlock, true );
|
||||
cubRead += pVarBlockInfo->m_cubBlock;
|
||||
}
|
||||
}
|
||||
|
||||
return cubRead;
|
||||
}
|
||||
|
||||
|
||||
int CRecordExternal::Init( CSchema *pSchema, uint8 *pubRecord, bool bTakeOwnership )
|
||||
{
|
||||
m_pSchema = pSchema;
|
||||
m_pubRecordFixedExternal = pubRecord;
|
||||
SetFlag( k_EAllocatedFixed, bTakeOwnership );
|
||||
SetFlag( k_EAllocatedVarBlock, bTakeOwnership );
|
||||
int cubRead = m_pSchema->CubRecordFixed() + CubRecordVarBlock();
|
||||
|
||||
return cubRead;
|
||||
}
|
||||
|
||||
CSchema *CRecordBase::GetPSchema()
|
||||
{
|
||||
return GetPSchemaImpl();
|
||||
}
|
||||
|
||||
CSchema *CRecordBase::GetPSchemaImpl()
|
||||
{
|
||||
CSchema *pSchema = NULL;
|
||||
int i = GetITable();
|
||||
if ( i != -1 )
|
||||
pSchema = &GSchemaFull().GetSchema( i );
|
||||
return pSchema;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Render a field to a buffer
|
||||
// Input: unColumn - field to render
|
||||
// cchBuffer - size of render buffer
|
||||
// pchBuffer - buffer to render into
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordBase::RenderField( uint32 unColumn, int cchBuffer, char *pchBuffer ) const
|
||||
{
|
||||
Q_strncpy( pchBuffer, "", cchBuffer );
|
||||
|
||||
uint8 *pubData;
|
||||
uint32 cubData;
|
||||
if ( !BGetField( unColumn, &pubData, &cubData ) )
|
||||
return;
|
||||
|
||||
// Get the column info and figure out how to interpret the data
|
||||
ConvertFieldToText( GetPRecordInfo()->GetColumnInfo( unColumn ).GetType(), pubData, cubData, pchBuffer, cchBuffer, false );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Reset to base state, freeing any memory we are responsible for
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordBase::Cleanup()
|
||||
{
|
||||
}
|
||||
|
||||
void CRecordVar::Cleanup()
|
||||
{
|
||||
// Must do this before freeing memory that encloses it
|
||||
// (eg releasing the net packet)
|
||||
if ( BFlagSet( k_EAllocatedVarBlock ) )
|
||||
{
|
||||
void *pvVarBlock = m_pSchema->PVarFieldBlockInfoFromRecord( PubRecordFixed() )->m_pubBlock;
|
||||
free( pvVarBlock );
|
||||
m_pSchema->PVarFieldBlockInfoFromRecord( PubRecordFixed() )->m_pubBlock = NULL;
|
||||
SetFlag( k_EAllocatedVarBlock, false );
|
||||
}
|
||||
}
|
||||
|
||||
void CRecordExternal::Cleanup()
|
||||
{
|
||||
// clean up the variable-length memory we might have allocated
|
||||
if ( BFlagSet( k_EAllocatedVarBlock ) )
|
||||
{
|
||||
void *pvVarBlock = m_pSchema->PVarFieldBlockInfoFromRecord( PubRecordFixed() )->m_pubBlock;
|
||||
free( pvVarBlock );
|
||||
m_pSchema->PVarFieldBlockInfoFromRecord( PubRecordFixed() )->m_pubBlock = NULL;
|
||||
SetFlag( k_EAllocatedVarBlock, false );
|
||||
}
|
||||
|
||||
// clean up the external memory we might have allocated
|
||||
if ( BFlagSet( k_EAllocatedFixed ) )
|
||||
free( m_pubRecordFixedExternal );
|
||||
SetFlag( k_EAllocatedFixed, false );
|
||||
m_pubRecordFixedExternal = NULL;
|
||||
|
||||
// clean up the lowest layer, not calling CRecordVar
|
||||
CRecordBase::Cleanup();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Deserializes a block of memory into this record
|
||||
// Input: pubData - Memory block to deserialize from
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordExternal::DeSerialize( uint8 *pubData )
|
||||
{
|
||||
InitFromBytes( pubData );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Calculates the size of this record when serialized
|
||||
// Output: Size of serialized message
|
||||
//-----------------------------------------------------------------------------
|
||||
uint32 CRecordBase::CubSerialized()
|
||||
{
|
||||
return CubRecordFixed() + CubRecordVarBlock();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get pointer to fixed part of record
|
||||
// Output: pubRecordFixed
|
||||
//-----------------------------------------------------------------------------
|
||||
uint8* CRecordBase::PubRecordFixed()
|
||||
{
|
||||
return ( uint8 * )( this + 1 );
|
||||
}
|
||||
|
||||
uint8* CRecordExternal::PubRecordFixed()
|
||||
{
|
||||
Assert( m_pubRecordFixedExternal );
|
||||
return m_pubRecordFixedExternal;
|
||||
}
|
||||
|
||||
uint8* CRecordVar::PubRecordFixed()
|
||||
{
|
||||
return ( uint8 * )( this + 1 );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get pointer to fixed part of record
|
||||
// Output: pubRecordFixed
|
||||
//-----------------------------------------------------------------------------
|
||||
const uint8* CRecordBase::PubRecordFixed() const
|
||||
{
|
||||
return const_cast<CRecordBase *>( this )->PubRecordFixed();
|
||||
}
|
||||
|
||||
const uint8* CRecordVar::PubRecordFixed() const
|
||||
{
|
||||
return const_cast<CRecordVar *>( this )->PubRecordFixed();
|
||||
}
|
||||
|
||||
const uint8* CRecordExternal::PubRecordFixed() const
|
||||
{
|
||||
return const_cast<CRecordExternal *>( this )->PubRecordFixed();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get size of fixed part of record
|
||||
// Output: size in bytes of fixed part
|
||||
//-----------------------------------------------------------------------------
|
||||
uint32 CRecordBase::CubRecordFixed() const
|
||||
{
|
||||
return GetPSchema()->CubRecordFixed();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get pointer to variable part of record
|
||||
// Output: Pointer to variable-length block -- may be NULL if this record
|
||||
// has no var-length fields or they are all empty
|
||||
//-----------------------------------------------------------------------------
|
||||
uint8* CRecordBase::PubRecordVarBlock()
|
||||
{
|
||||
VarFieldBlockInfo_t *pVarFieldBlockInfo = GetPSchema()->PVarFieldBlockInfoFromRecord( PubRecordFixed() );
|
||||
if ( pVarFieldBlockInfo )
|
||||
{
|
||||
return pVarFieldBlockInfo->m_pubBlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get pointer to variable part of record
|
||||
// Output: Pointer to variable-length block -- may be NULL if this record
|
||||
// has no var-length fields or they are all empty
|
||||
//-----------------------------------------------------------------------------
|
||||
const uint8* CRecordBase::PubRecordVarBlock() const
|
||||
{
|
||||
return const_cast<CRecordBase *>( this )->PubRecordVarBlock();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get size of variable part of record
|
||||
// Output: Size in bytes of var-length block - may be zero if this record
|
||||
// has no var-length fields or they are all empty
|
||||
//-----------------------------------------------------------------------------
|
||||
uint32 CRecordBase::CubRecordVarBlock() const
|
||||
{
|
||||
VarFieldBlockInfo_t *pVarFieldBlockInfo = GetPSchema()->PVarFieldBlockInfoFromRecord( PubRecordFixed() );
|
||||
if ( pVarFieldBlockInfo )
|
||||
{
|
||||
return pVarFieldBlockInfo->m_cubBlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get size of variable part of record
|
||||
// Output: Size in bytes of var-length block - may be zero if this record
|
||||
// has no var-length fields or they are all empty
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CRecordBase::BAssureRecordVarStorage( uint32 cVariableBytes )
|
||||
{
|
||||
// get the variable field block
|
||||
VarFieldBlockInfo_t *pVarFieldBlockInfo = GetPSchema()->PVarFieldBlockInfoFromRecord( PubRecordFixed() );
|
||||
if ( pVarFieldBlockInfo )
|
||||
{
|
||||
// if we have it, see if it's got enough storage
|
||||
if ( pVarFieldBlockInfo->m_cubBlock >= cVariableBytes )
|
||||
{
|
||||
// already there
|
||||
return true;
|
||||
}
|
||||
|
||||
// allocate it
|
||||
uint8* pubData = (uint8*) malloc( cVariableBytes );
|
||||
if ( pubData == NULL )
|
||||
return false;
|
||||
|
||||
// do we have something right now?
|
||||
if ( pVarFieldBlockInfo->m_cubBlock != 0 )
|
||||
{
|
||||
// sure do. copy it over.
|
||||
Q_memcpy( pubData, pVarFieldBlockInfo->m_pubBlock, pVarFieldBlockInfo->m_cubBlock );
|
||||
|
||||
// free what was there
|
||||
free( pVarFieldBlockInfo->m_pubBlock );
|
||||
}
|
||||
|
||||
// hook up our buffer
|
||||
pVarFieldBlockInfo->m_cubBlockFree = cVariableBytes - pVarFieldBlockInfo->m_cubBlock;
|
||||
pVarFieldBlockInfo->m_cubBlock = cVariableBytes;
|
||||
pVarFieldBlockInfo->m_pubBlock = pubData;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we don't have one;
|
||||
// we've got no variable length fields, and so can't preallocate for them!
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Initialize this whole record to random data
|
||||
// Input: unPrimaryIndex - Primary index to set
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordExternal::InitRecordRandom( uint32 unPrimaryIndex )
|
||||
{
|
||||
bool bRealloced = false;
|
||||
GetPSchema()->InitRecordRandom( PubRecordFixed(), unPrimaryIndex, &bRealloced, BFlagSet( k_EAllocatedVarBlock ) );
|
||||
|
||||
if ( bRealloced )
|
||||
SetFlag( k_EAllocatedVarBlock, true );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Set a field in this record to random bits
|
||||
// Input: iField - Field to set
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordExternal::SetFieldRandom( int iField )
|
||||
{
|
||||
bool bRealloced = false;
|
||||
GetPSchema()->SetFieldRandom( PubRecordFixed(), iField, &bRealloced, BFlagSet( k_EAllocatedVarBlock ) );
|
||||
|
||||
if ( bRealloced )
|
||||
SetFlag( k_EAllocatedVarBlock, true );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get a field (var or fixed) from this record
|
||||
// Input: iField - Field to set
|
||||
// ppubData - Receives pointer to fields data
|
||||
// pcubField - Receives count of bytes of data (will count the null for strings)
|
||||
// Output: true if succeeds
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CRecordBase::BGetField( int iField, uint8 **ppubData, uint32 *pcubField ) const
|
||||
{
|
||||
return GetPSchema()->BGetFieldData( PubRecordFixed(), iField, ppubData, pcubField );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Sets the data for a field, whether fixed or variable length
|
||||
// Input: iField - index of field to set
|
||||
// pubData - pointer to field data to copy from
|
||||
// cubData - size in bytes of that data
|
||||
// Output: true if successful
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CRecordBase::BSetField( int iField, void *pvData, uint32 cubData )
|
||||
{
|
||||
bool bRealloced = false;
|
||||
bool bResult = BSetField( iField, pvData, cubData, &bRealloced );
|
||||
Assert( !bRealloced );
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
bool CRecordBase::BSetField( int iField, void *pvData, uint32 cubData, bool *pbRealloced )
|
||||
{
|
||||
uint8 *pubData = reinterpret_cast<uint8 *>( pvData );
|
||||
|
||||
if ( !GetPSchema()->BSetFieldData( PubRecordFixed(), iField, pubData, cubData, pbRealloced ) )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CRecordVar::BSetField( int iField, void *pvData, uint32 cubData )
|
||||
{
|
||||
bool bRealloced = false;
|
||||
bool bResult = CRecordBase::BSetField( iField, pvData, cubData, &bRealloced );
|
||||
|
||||
if ( bRealloced )
|
||||
SetFlag( k_EAllocatedVarBlock, true );
|
||||
return bResult;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Erases a field, setting it to 0 length (if possible) and filling with nulls
|
||||
// Input: iField - index of field to wipe
|
||||
// NOTE: This relies on CSchema::BSetFieldData nulling out the rest of a field when it is set to 0 length!
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordBase::WipeField( int iField )
|
||||
{
|
||||
bool bRealloced = false;
|
||||
|
||||
// Empty Data
|
||||
uint32 un = 0;
|
||||
|
||||
// Length should be 0, except for non-variable length strings where length should be 1 (for an empty string "")
|
||||
int cub = 0;
|
||||
Field_t &field = GetPSchema()->GetField( iField );
|
||||
Assert( !field.BIsVariableLength() );
|
||||
if ( field.BIsStringType() )
|
||||
cub = 1;
|
||||
|
||||
GetPSchema()->BSetFieldData( PubRecordFixed(), iField, ( uint8 * ) &un, cub, &bRealloced );
|
||||
|
||||
Assert( !bRealloced );
|
||||
}
|
||||
|
||||
void CRecordVar::WipeField( int iField )
|
||||
{
|
||||
bool bRealloced = false;
|
||||
|
||||
// Empty Data
|
||||
uint32 un = 0;
|
||||
|
||||
// Length should be 0, except for non-variable length strings where length should be 1 (for an empty string "")
|
||||
int cub = 0;
|
||||
Field_t &field = GetPSchema()->GetField( iField );
|
||||
if ( field.BIsStringType() && !field.BIsVariableLength() )
|
||||
cub = 1;
|
||||
|
||||
GetPSchema()->BSetFieldData( PubRecordFixed(), iField, ( uint8 * ) &un, cub, &bRealloced );
|
||||
|
||||
if ( bRealloced )
|
||||
SetFlag( k_EAllocatedVarBlock, true );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get a string field - will return empty string instead of NULL if field has no datas
|
||||
// Input: iField - Field to get
|
||||
// pcubField - Receives count of bytes of data (will count the null for strings)
|
||||
// Output: const pointer to string data (to an empty string if no data)
|
||||
//-----------------------------------------------------------------------------
|
||||
const char * CRecordBase::GetStringField( int iField, uint32 *pcubField )
|
||||
{
|
||||
uint8 * pubData = NULL;
|
||||
*pcubField = 0;
|
||||
|
||||
if ( BGetField( iField, &pubData, pcubField ) && *pcubField > 0 )
|
||||
return ( const char * ) pubData;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get an int field
|
||||
// Input: iField - Field to get
|
||||
// Output: Int (0 if no data)
|
||||
//-----------------------------------------------------------------------------
|
||||
int CRecordBase::GetInt( int iField )
|
||||
{
|
||||
return ( int ) GetUint32( iField );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get a uint16 field
|
||||
// Input: iField - Field to get
|
||||
// Output: uint16 (0 if no data)
|
||||
//-----------------------------------------------------------------------------
|
||||
uint16 CRecordBase::GetUint16( int iField )
|
||||
{
|
||||
uint8 * pubData = NULL;
|
||||
uint32 cubField = 0;
|
||||
|
||||
DbgVerify( BGetField( iField, &pubData, &cubField ) );
|
||||
Assert( 0 < cubField );
|
||||
|
||||
if ( NULL != pubData )
|
||||
return *( uint16 * ) pubData;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get a uint32 field
|
||||
// Input: iField - Field to get
|
||||
// Output: uint32 (0 if no data)
|
||||
//-----------------------------------------------------------------------------
|
||||
uint32 CRecordBase::GetUint32( int iField )
|
||||
{
|
||||
uint8 * pubData = NULL;
|
||||
uint32 cubField = 0;
|
||||
|
||||
DbgVerify( BGetField( iField, &pubData, &cubField ) );
|
||||
Assert( 0 < cubField );
|
||||
|
||||
if ( NULL != pubData )
|
||||
return *( uint32 * ) pubData;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get a uint64 field
|
||||
// Input: iField - Field to get
|
||||
// Output: uint64 (0 if no data)
|
||||
//-----------------------------------------------------------------------------
|
||||
uint64 CRecordBase::GetUint64( int iField )
|
||||
{
|
||||
uint8 * pubData = NULL;
|
||||
uint32 cubField = 0;
|
||||
|
||||
DbgVerify( BGetField( iField, &pubData, &cubField ) );
|
||||
Assert( 0 < cubField );
|
||||
|
||||
if ( NULL != pubData )
|
||||
return *( uint64 * ) pubData;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const char * CRecordBase::ReadVarCharField( const CVarCharField &field ) const
|
||||
{
|
||||
Assert( false );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const uint8 * CRecordBase::ReadVarDataField( const CVarField &field, uint32 *pcubField ) const
|
||||
{
|
||||
Assert( false );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// These may cause a realloc
|
||||
bool CRecordBase::SetVarCharField( CVarCharField &field, const char *pchString, bool bTruncate, int32 iField )
|
||||
{
|
||||
Assert( false );
|
||||
return false ;
|
||||
}
|
||||
|
||||
void CRecordBase::SetVarDataField( CVarField &field, const void *pvData, uint32 cubData )
|
||||
{
|
||||
Assert( false );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Read data from a varchar field
|
||||
// Input: field - opaque field object to read from
|
||||
// Output: pointer to data - may be NULL if that field is empty.
|
||||
//-----------------------------------------------------------------------------
|
||||
const char * CRecordVar::ReadVarCharField( const CVarCharField &field ) const
|
||||
{
|
||||
Assert ( GetPSchema()->BHasVariableFields() );
|
||||
|
||||
uint8 *pubData;
|
||||
uint32 cubData;
|
||||
if ( GetPSchema()->BGetVarField( PubRecordFixed(), &field, &pubData, &cubData ) )
|
||||
return (const char *)pubData;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Read data from a vardata field
|
||||
// Input: field - opaque field object to read from
|
||||
// Output: pointer to data - may be NULL if that field is empty.
|
||||
//-----------------------------------------------------------------------------
|
||||
const uint8 *CRecordVar::ReadVarDataField( const CVarField &field, uint32 *pcubField ) const
|
||||
{
|
||||
Assert ( GetPSchema()->BHasVariableFields() );
|
||||
|
||||
uint8 *pubData;
|
||||
*pcubField = 0;
|
||||
if ( GetPSchema()->BGetVarField( PubRecordFixed(), &field, &pubData, pcubField ) )
|
||||
return pubData;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Update (in memory) a varchar field
|
||||
// Input: field - opaque field object to update
|
||||
// pchString - string data to set
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CRecordVar::SetVarCharField( CVarCharField &field, const char *pchString, bool bTruncate, int32 iField )
|
||||
{
|
||||
Assert ( GetPSchema()->BHasVariableFields() );
|
||||
if( iField < 0 )
|
||||
{
|
||||
AssertMsg1( false, "Encountered a bad call to SetVarCharField with an invalid field specified: %d", iField );
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bTruncated = false;
|
||||
int cchLen = Q_strlen( pchString ) + 1;
|
||||
|
||||
// since we're a VARCHAR field, cbMaxLength is the length in characters
|
||||
const int cchMaxLength = m_pSchema->GetField( iField ).m_cchMaxLength;
|
||||
if ( ( cchMaxLength > 0 ) && ( cchLen > cchMaxLength ) )
|
||||
{
|
||||
if( bTruncate )
|
||||
{
|
||||
bTruncated = true;
|
||||
cchLen = cchMaxLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
// caller should check his data and not pass stuff that wont fit
|
||||
AssertMsg4( false, "Overflow in SetVarCharField (%u > %u) for column %s in table %s", cchLen, cchMaxLength, m_pSchema->GetField( iField ).m_rgchName, m_pSchema->GetPchName() );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool bRealloced = false;
|
||||
bool fSuccess = GetPSchema()->BSetVarField( PubRecordFixed(), &field, pchString, cchLen, &bRealloced, BFlagSet( k_EAllocatedVarBlock ) );
|
||||
if( fSuccess && bTruncated )
|
||||
{
|
||||
//make sure the last character is NULL if we truncated
|
||||
VarFieldBlockInfo_t *pBlock = GetPSchema()->PVarFieldBlockInfoFromRecord( PubRecordFixed() );
|
||||
uint8 *pubVarBlock = pBlock->m_pubBlock;
|
||||
char* pField = ( char* )( pubVarBlock + field.m_dubOffset );
|
||||
pField[ cchLen - 1 ] = '\0';
|
||||
}
|
||||
|
||||
if ( bRealloced )
|
||||
SetFlag( k_EAllocatedVarBlock, true );
|
||||
return fSuccess;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Update (in memory) a vardata field
|
||||
// Input: field - opaque field object to update
|
||||
// pvData - pointer to data to put there
|
||||
// cubData - size in bytes of the data
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordVar::SetVarDataField( CVarField &field, const void *pvData, uint32 cubData )
|
||||
{
|
||||
Assert ( GetPSchema()->BHasVariableFields() );
|
||||
|
||||
bool bRealloced = false;
|
||||
GetPSchema()->BSetVarField( PubRecordFixed(), &field, pvData, cubData, &bRealloced, BFlagSet( k_EAllocatedVarBlock ) );
|
||||
|
||||
if ( bRealloced )
|
||||
SetFlag( k_EAllocatedVarBlock, true );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Set or clear the specified flag in m_nFlags
|
||||
// Input: eFlag - flag (single bit) to change
|
||||
// bSet - Set it, else clear it
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordVar::SetFlag( int eFlag, bool bSet )
|
||||
{
|
||||
if ( bSet )
|
||||
m_nFlags |= eFlag;
|
||||
else
|
||||
m_nFlags &= ~eFlag;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the state of the specified flag
|
||||
// Input: eFlag - flag (single bit) to check
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CRecordVar::BFlagSet( int eFlag ) const
|
||||
{
|
||||
return 0 != ( m_nFlags & eFlag );
|
||||
}
|
||||
|
||||
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Run a global validation pass on all of our data structures and memory
|
||||
// allocations.
|
||||
// Input: validator - Our global validator object
|
||||
// pchName - Our name (typically a member var in our container)
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordBase::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
VALIDATE_SCOPE();
|
||||
}
|
||||
|
||||
void CRecordVar::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
VALIDATE_SCOPE();
|
||||
|
||||
if ( BFlagSet( k_EAllocatedVarBlock ) )
|
||||
{
|
||||
validator.ClaimMemory( GetPSchema()->PVarFieldBlockInfoFromRecord( PubRecordFixed() )->m_pubBlock );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CRecordExternal::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
if ( BFlagSet( k_EAllocatedFixed ) )
|
||||
{
|
||||
validator.ClaimMemory( m_pubRecordFixedExternal );
|
||||
}
|
||||
|
||||
CRecordBase::Validate( validator, pchName );
|
||||
}
|
||||
|
||||
void CRecordBase::ValidateStatics( CValidator &validator, const char *pchName )
|
||||
{
|
||||
VALIDATE_SCOPE_STATIC( "CRecordBase class statics" );
|
||||
}
|
||||
|
||||
#endif // DBGFLAG_VALIDATE
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Return the schema for this record type
|
||||
//-----------------------------------------------------------------------------
|
||||
CSchema *CRecordType::GetSchema() const
|
||||
{
|
||||
return &GSchemaFull().GetSchema( GetITable() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Return the CRecordInfo for this record type
|
||||
//-----------------------------------------------------------------------------
|
||||
CRecordInfo *CRecordType::GetRecordInfo() const
|
||||
{
|
||||
return GetSchema()->GetRecordInfo();
|
||||
}
|
||||
|
||||
} // namespace GCSDK
|
||||
@@ -0,0 +1,915 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
//#include "sqlaccess.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
// Memory pool for CRecordInfo
|
||||
CThreadSafeClassMemoryPool<CRecordInfo> CRecordInfo::sm_MemPoolRecordInfo( 10, UTLMEMORYPOOL_GROW_FAST );
|
||||
|
||||
#ifdef _DEBUG
|
||||
// validation tracking
|
||||
CUtlRBTree<CRecordInfo *, int > CRecordInfo::sm_mapPMemPoolRecordInfo( DefLessFunc( CRecordInfo *) );
|
||||
CThreadMutex CRecordInfo::sm_mutexMemPoolRecordInfo;
|
||||
#endif
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// determine if this fieldset is equal to the other one
|
||||
//-----------------------------------------------------------------------------
|
||||
/* static */
|
||||
bool FieldSet_t::CompareFieldSets( const FieldSet_t& refThis, CRecordInfo* pRecordInfoThis,
|
||||
const FieldSet_t& refOther, CRecordInfo* pRecordInfoOther )
|
||||
{
|
||||
// same number of columns?
|
||||
int cColumns = refThis.GetCount();
|
||||
if ( refOther.GetCount() != cColumns )
|
||||
return false;
|
||||
|
||||
int cIncludedColumns = refThis.GetIncludedCount();
|
||||
if ( refOther.GetIncludedCount() != cIncludedColumns )
|
||||
return false;
|
||||
|
||||
// do the regular columns first; this is order-dependent
|
||||
for ( int m = 0; m < cColumns; m++ )
|
||||
{
|
||||
int nThisField = refThis.GetField( m );
|
||||
const CColumnInfo& refThisColumn = pRecordInfoThis->GetColumnInfo( nThisField );
|
||||
|
||||
int nOtherField = refOther.GetField( m );
|
||||
const CColumnInfo& refOtherColumn = pRecordInfoOther->GetColumnInfo( nOtherField );
|
||||
|
||||
if ( refOtherColumn != refThisColumn )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// do the included columns now; order independent
|
||||
for ( int m = 0; m < cIncludedColumns; m++ )
|
||||
{
|
||||
int nThisField = refThis.GetIncludedField( m );
|
||||
const CColumnInfo& refThisColumn = pRecordInfoThis->GetColumnInfo( nThisField );
|
||||
bool bFoundMatch = false;
|
||||
|
||||
for ( int n = 0; n < cIncludedColumns; n++ )
|
||||
{
|
||||
int nOtherField = refOther.GetIncludedField( n );
|
||||
const CColumnInfo& refOtherColumn = pRecordInfoOther->GetColumnInfo( nOtherField );
|
||||
|
||||
if ( refOtherColumn == refThisColumn )
|
||||
{
|
||||
bFoundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !bFoundMatch )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CRecordInfo::CRecordInfo()
|
||||
: m_MapIColumnInfo( 0, 0, CaselessStringLessThan )
|
||||
{
|
||||
m_rgchName[0] = 0;
|
||||
m_bPreparedForUse = false;
|
||||
m_bAllColumnsAdded = false;
|
||||
m_bHaveChecksum = false;
|
||||
m_bHaveColumnNameIndex = false;
|
||||
m_nHasPrimaryKey = k_EPrimaryKeyTypeNone;
|
||||
m_iPKIndex = -1;
|
||||
m_cubFixedSize = 0;
|
||||
m_nChecksum = 0;
|
||||
m_eSchemaCatalog = k_ESchemaCatalogInvalid;
|
||||
m_nTableID = 0;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Initializes this record info from DS equivalent information
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordInfo::InitFromDSSchema( CSchema *pSchema )
|
||||
{
|
||||
// copy the name over
|
||||
SetName( pSchema->GetPchName() );
|
||||
|
||||
// copy each of the fields, preallocating capacity
|
||||
int cFields = pSchema->GetCField();
|
||||
m_VecColumnInfo.EnsureCapacity( cFields );
|
||||
for ( int iField = 0; iField < cFields; iField++ )
|
||||
{
|
||||
Field_t &field = pSchema->GetField( iField );
|
||||
AddColumn( field.m_rgchSQLName, iField+1, field.m_EType, field.m_cubLength, field.m_nColFlags, field.m_cchMaxLength );
|
||||
}
|
||||
|
||||
m_nTableID = pSchema->GetITable();
|
||||
|
||||
// copy the list of PK index fields
|
||||
m_iPKIndex = pSchema->GetPKIndex( );
|
||||
|
||||
// copy the list of Indexes
|
||||
m_VecIndexes = pSchema->GetIndexes( );
|
||||
|
||||
// which schema?
|
||||
m_eSchemaCatalog = pSchema->GetESchemaCatalog();
|
||||
|
||||
// copy full-text column list
|
||||
// and the index of the catalog it will create on
|
||||
m_vecFTSFields = pSchema->GetFTSColumns();
|
||||
m_nFullTextCatalogIndex = pSchema->GetFTSIndexCatalog();
|
||||
|
||||
// Copy over the FK data
|
||||
int cFKs = pSchema->GetFKCount();
|
||||
for ( int i = 0; i < cFKs; ++i )
|
||||
{
|
||||
FKData_t &fkData = pSchema->GetFKData( i );
|
||||
AddFK( fkData );
|
||||
}
|
||||
|
||||
// prepare for use
|
||||
PrepareForUse( );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Adds a new column to this record info
|
||||
// Input: pchName - column name
|
||||
// nSQLColumn - column index in SQL to bind to (1-based)
|
||||
// eType - data type of column
|
||||
// cubFixedSize - for fixed-size fields, the size
|
||||
// nColFlags - attributes
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordInfo::AddColumn( const char *pchName, int nSQLColumn, EGCSQLType eType, int cubFixedSize, int nColFlags, int cchMaxSize )
|
||||
{
|
||||
Assert( !m_bPreparedForUse );
|
||||
if ( m_bPreparedForUse )
|
||||
return;
|
||||
uint32 unColumn = m_VecColumnInfo.AddToTail();
|
||||
CColumnInfo &columnInfo = m_VecColumnInfo[unColumn];
|
||||
|
||||
columnInfo.Set( pchName, nSQLColumn, eType, cubFixedSize, nColFlags, cchMaxSize );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Adds a new FK to this record info
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordInfo::AddFK( const FKData_t &fkData )
|
||||
{
|
||||
m_VecFKData.AddToTail( fkData );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: compare function to sort by column name
|
||||
//-----------------------------------------------------------------------------
|
||||
int __cdecl CompareColumnInfo( const CColumnInfo *pColumnInfoLeft, const CColumnInfo *pColumnInfoRight )
|
||||
{
|
||||
const char *pchLeft = ( (CColumnInfo *) pColumnInfoLeft )->GetName();
|
||||
const char *pchRight = ( (CColumnInfo *) pColumnInfoRight )->GetName();
|
||||
Assert( pchLeft && pchLeft[0] );
|
||||
Assert( pchRight && pchRight[0] );
|
||||
return Q_stricmp( pchLeft, pchRight );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: compares this record info to another record info
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CRecordInfo::EqualTo( CRecordInfo* pOther )
|
||||
{
|
||||
int nOurs = GetChecksum();
|
||||
int nTheirs = pOther->GetChecksum();
|
||||
|
||||
// if this much isn't equal, we're no good
|
||||
if (nOurs != nTheirs)
|
||||
return false;
|
||||
|
||||
if ( !CompareIndexLists( pOther ) )
|
||||
return false;
|
||||
|
||||
if ( !CompareFKs( pOther ) )
|
||||
return false;
|
||||
|
||||
return CompareFTSIndexLists( pOther );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: format the index list into a string
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordInfo::GetIndexFieldList( CFmtStr1024 *pstr, int nIndents ) const
|
||||
{
|
||||
// table name at first
|
||||
pstr->sprintf( "Table %s:\n", this->GetName() );
|
||||
|
||||
// for each of the indexes ...
|
||||
for ( int n = 0; n < m_VecIndexes.Count(); n++ )
|
||||
{
|
||||
const FieldSet_t& fs = m_VecIndexes[n];
|
||||
|
||||
// indent enough
|
||||
for ( int x = 0; x < nIndents; x++ )
|
||||
{
|
||||
pstr->Append( "\t" );
|
||||
}
|
||||
|
||||
// show if it is clustered or not
|
||||
pstr->AppendFormat( "Index %d (%s): %sclustered, %sunique {", n,
|
||||
fs.GetIndexName(),
|
||||
fs.IsClustered() ? "" : "non-",
|
||||
fs.IsUnique() ? "" : "non-" );
|
||||
|
||||
// then show all the columns
|
||||
for (int m = 0; m < fs.GetCount(); m++ )
|
||||
{
|
||||
int x = fs.GetField( m );
|
||||
const char* pstrName = m_VecColumnInfo[x].GetName();
|
||||
pstr->AppendFormat( "%s %s", ( m == 0 ) ? "" : ",", pstrName );
|
||||
}
|
||||
|
||||
// then the included columns, too
|
||||
for ( int m = 0; m < fs.GetIncludedCount(); m++ )
|
||||
{
|
||||
int x = fs.GetIncludedField( m );
|
||||
const char* pstrName = m_VecColumnInfo[x].GetName();
|
||||
pstr->AppendFormat( ", *%s", pstrName );
|
||||
}
|
||||
pstr->Append( " }\n" );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the number of foreign key constraints defined for the table
|
||||
//-----------------------------------------------------------------------------
|
||||
int CRecordInfo::GetFKCount()
|
||||
{
|
||||
return m_VecFKData.Count();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get data for a foreign key by index (valid for 0...GetFKCount()-1)
|
||||
//-----------------------------------------------------------------------------
|
||||
FKData_t &CRecordInfo::GetFKData( int iIndex )
|
||||
{
|
||||
return m_VecFKData[iIndex];
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: format the FK list into a string
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordInfo::GetFKListString( CFmtStr1024 *pstr, int nIndents )
|
||||
{
|
||||
// table name at first
|
||||
pstr->sprintf( "Table %s Foreign Keys: \n", this->GetName() );
|
||||
|
||||
|
||||
|
||||
if ( m_VecFKData.Count() == 0 )
|
||||
{
|
||||
// indent enough
|
||||
pstr->AppendIndent( nIndents );
|
||||
pstr->Append( "No foreign keys for table\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
for ( int n = 0; n < m_VecFKData.Count(); n++ )
|
||||
{
|
||||
// indent enough
|
||||
pstr->AppendIndent( nIndents );
|
||||
|
||||
FKData_t &fkData = m_VecFKData[n];
|
||||
CFmtStr sColumns, sParentColumns;
|
||||
FOR_EACH_VEC( fkData.m_VecColumnRelations, i )
|
||||
{
|
||||
FKColumnRelation_t &colRelation = fkData.m_VecColumnRelations[i];
|
||||
if ( i > 0)
|
||||
{
|
||||
sColumns += ",";
|
||||
sParentColumns += ",";
|
||||
}
|
||||
sColumns += colRelation.m_rgchCol;
|
||||
sParentColumns += colRelation.m_rgchParentCol;
|
||||
}
|
||||
|
||||
pstr->AppendFormat( "CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s(%s) ON DELETE %s ON UPDATE %s\n",
|
||||
fkData.m_rgchName, sColumns.Access(), fkData.m_rgchParentTableName, sParentColumns.Access(),
|
||||
PchNameFromEForeignKeyAction( fkData.m_eOnDeleteAction ), PchNameFromEForeignKeyAction( fkData.m_eOnUpdateAction ) );
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordInfo::AddFTSFields( CUtlVector< int > &vecFields )
|
||||
{
|
||||
AssertMsg( m_vecFTSFields.Count() == 0, "Only one FTS index per table" );
|
||||
FOR_EACH_VEC( vecFields, n )
|
||||
{
|
||||
int nField = vecFields[n];
|
||||
m_vecFTSFields.AddToTail( nField );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: compares FK lists in this record with those of another
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CRecordInfo::CompareFKs( CRecordInfo *pOther )
|
||||
{
|
||||
if ( pOther->m_VecFKData.Count() != m_VecFKData.Count() )
|
||||
return false;
|
||||
|
||||
for( int i=0; i < m_VecFKData.Count(); ++i )
|
||||
{
|
||||
FKData_t &fkDataMine = m_VecFKData[i];
|
||||
|
||||
bool bFoundInOther = false;
|
||||
for ( int j=0; j < pOther->m_VecFKData.Count(); ++j )
|
||||
{
|
||||
FKData_t &fkDataOther = pOther->m_VecFKData[j];
|
||||
|
||||
if ( fkDataMine == fkDataOther )
|
||||
{
|
||||
bFoundInOther = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !bFoundInOther )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Locate an index by its properties (ignoring the name).
|
||||
// Returns position of index in the index array, or -1 if not found.
|
||||
//-----------------------------------------------------------------------------
|
||||
int CRecordInfo::FindIndex( CRecordInfo *pRec, const FieldSet_t& fieldSet )
|
||||
{
|
||||
for ( int i = 0; i < m_VecIndexes.Count(); i++ )
|
||||
{
|
||||
if ( FieldSet_t::CompareFieldSets( m_VecIndexes[i], this, fieldSet, pRec ) )
|
||||
return i;
|
||||
}
|
||||
|
||||
// Not found
|
||||
return -1;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Locate an index with the given name.
|
||||
// Returns position of index in the index array, or -1 if not found.
|
||||
//-----------------------------------------------------------------------------
|
||||
int CRecordInfo::FindIndexByName( const char *pszName ) const
|
||||
{
|
||||
for ( int i = 0; i < m_VecIndexes.Count(); i++ )
|
||||
{
|
||||
if ( V_stricmp( m_VecIndexes[i].GetIndexName(), pszName )== 0 )
|
||||
return i;
|
||||
}
|
||||
|
||||
// Not found
|
||||
return -1;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: compares index lists in this record with those of another
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CRecordInfo::CompareIndexLists( CRecordInfo* pOther )
|
||||
{
|
||||
// compare the index lists (but don't use CRCs)
|
||||
|
||||
// different size? can't be the same
|
||||
if ( pOther->GetIndexFieldCount() != GetIndexFieldCount() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// We have to loop through both lists of indexes and try to find a match.
|
||||
// We also must make sure the match is exact, and that no previous match
|
||||
// can alias another attempt at a match. Pretty messy, but with no available
|
||||
// identity over index objects, we're forced to a suboptimal solution.
|
||||
|
||||
int nIndexes = GetIndexFieldCount();
|
||||
|
||||
// get a copy of the other index vector, which we'll remove items from as
|
||||
// matches are found.
|
||||
|
||||
CUtlVector<FieldSet_t> vecOtherIndexes;
|
||||
vecOtherIndexes.CopyArray( pOther->GetIndexFields().Base(), nIndexes );
|
||||
|
||||
for ( int nOurs = 0; nOurs < nIndexes; nOurs++ )
|
||||
{
|
||||
int nOtherMatchIndex = -1;
|
||||
const FieldSet_t& refOurs = GetIndexFields()[nOurs];
|
||||
|
||||
// rip through copy of other to find one that matches
|
||||
for ( int nOther = 0; nOther < vecOtherIndexes.Count(); nOther++ )
|
||||
{
|
||||
const FieldSet_t& refOther = vecOtherIndexes[nOther];
|
||||
if ( FieldSet_t::CompareFieldSets( refOurs, this, refOther, pOther ) )
|
||||
{
|
||||
nOtherMatchIndex = nOther;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( nOtherMatchIndex >= 0 )
|
||||
{
|
||||
// this works! remove it from other copy
|
||||
vecOtherIndexes.Remove( nOtherMatchIndex );
|
||||
}
|
||||
else
|
||||
{
|
||||
// something didn't match, so bail out early
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: compares full-text indexes for this record with those of another
|
||||
// column order in an FTS is irrelevant, so this is a simple match
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CRecordInfo::CompareFTSIndexLists( CRecordInfo* pOther ) const
|
||||
{
|
||||
// compare full-text index columns
|
||||
if ( m_vecFTSFields.Count() != pOther->m_vecFTSFields.Count() )
|
||||
{
|
||||
// counts don't match, so obviously no good
|
||||
return false;
|
||||
}
|
||||
for ( int nColumnIndex = 0; nColumnIndex < m_vecFTSFields.Count(); nColumnIndex++ )
|
||||
{
|
||||
bool bFound = false;
|
||||
for ( int nInnerIndex = 0; nInnerIndex < pOther->m_vecFTSFields.Count(); nInnerIndex++ )
|
||||
{
|
||||
if ( m_vecFTSFields[nInnerIndex] == pOther->m_vecFTSFields[nColumnIndex] )
|
||||
{
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !bFound )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns the checksum for this record info
|
||||
//-----------------------------------------------------------------------------
|
||||
int CRecordInfo::GetChecksum()
|
||||
{
|
||||
Assert( m_bPreparedForUse );
|
||||
|
||||
// calculate it now if we haven't already
|
||||
if ( !m_bHaveChecksum )
|
||||
CalculateChecksum();
|
||||
|
||||
return m_nChecksum;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Prepares this object for use after all columns have been added
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordInfo::PrepareForUse()
|
||||
{
|
||||
Assert( !m_bPreparedForUse );
|
||||
Assert( 0 == m_cubFixedSize );
|
||||
Assert( 0 == m_nChecksum );
|
||||
|
||||
SetAllColumnsAdded();
|
||||
|
||||
FOR_EACH_VEC( m_VecColumnInfo, nColumn )
|
||||
{
|
||||
CColumnInfo &columnInfo = m_VecColumnInfo[nColumn];
|
||||
|
||||
// keep track of total fixed size of all columns
|
||||
if ( !columnInfo.BIsVariableLength() )
|
||||
m_cubFixedSize += columnInfo.GetFixedSize();
|
||||
|
||||
if ( columnInfo.BIsPrimaryKey() )
|
||||
{
|
||||
// a PK column! if we have seen one before,
|
||||
// know we have a-column PK; otherwise, a single column PK
|
||||
if (m_nHasPrimaryKey == k_EPrimaryKeyTypeNone)
|
||||
m_nHasPrimaryKey = k_EPrimaryKeyTypeSingle;
|
||||
else
|
||||
m_nHasPrimaryKey = k_EPrimaryKeyTypeMulti;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure count matches the enum
|
||||
/*
|
||||
Assert( ( m_nHasPrimaryKey == k_EPrimaryKeyTypeNone && m_VecPKFields.Count() == 0 ) ||
|
||||
( m_nHasPrimaryKey == k_EPrimaryKeyTypeMulti && m_VecPKFields.Count() > 1) ||
|
||||
( m_nHasPrimaryKey == k_EPrimaryKeyTypeSingle && m_VecPKFields.Count() == 1) );
|
||||
*/
|
||||
|
||||
m_bPreparedForUse = true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns index of column with specified name
|
||||
// Input: pchName - column name
|
||||
// punColumn - pointer to fill in with index
|
||||
// Output: return true if found, false otherwise
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CRecordInfo::BFindColumnByName( const char *pchName, int *punColumn )
|
||||
{
|
||||
Assert( m_bAllColumnsAdded );
|
||||
Assert( pchName && *pchName );
|
||||
Assert( punColumn );
|
||||
|
||||
*punColumn = -1;
|
||||
|
||||
// if we haven't already built the name index, build it now
|
||||
if ( !m_bHaveColumnNameIndex )
|
||||
BuildColumnNameIndex();
|
||||
|
||||
*punColumn = m_MapIColumnInfo.Find( pchName );
|
||||
return ( m_MapIColumnInfo.InvalidIndex() != *punColumn );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Sets the name of this record info
|
||||
// Input: pchName - name
|
||||
// Notes: record info that describes a table will have a name (the table name);
|
||||
// record info that describes a result set will not
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordInfo::SetName( const char *pchName )
|
||||
{
|
||||
Assert( pchName && *pchName );
|
||||
Assert( !m_bPreparedForUse ); // don't change this after prepared for use
|
||||
Q_strncpy( m_rgchName, pchName, Q_ARRAYSIZE( m_rgchName ) );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Builds the column name index for fast lookup by name
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordInfo::BuildColumnNameIndex()
|
||||
{
|
||||
AUTO_LOCK( m_Mutex );
|
||||
|
||||
if ( m_bHaveColumnNameIndex )
|
||||
return;
|
||||
|
||||
Assert( m_bAllColumnsAdded );
|
||||
|
||||
Assert( 0 == m_MapIColumnInfo.Count() );
|
||||
|
||||
FOR_EACH_VEC( m_VecColumnInfo, nColumn )
|
||||
{
|
||||
// build name->column index map
|
||||
CColumnInfo &columnInfo = m_VecColumnInfo[nColumn];
|
||||
m_MapIColumnInfo.Insert( columnInfo.GetName(), nColumn );
|
||||
}
|
||||
m_bHaveColumnNameIndex = true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Calculates the checksum for this record info
|
||||
//-----------------------------------------------------------------------------
|
||||
void CRecordInfo::CalculateChecksum()
|
||||
{
|
||||
AUTO_LOCK( m_Mutex );
|
||||
|
||||
if ( m_bHaveChecksum )
|
||||
return;
|
||||
|
||||
// build the column name index if necessary
|
||||
if ( !m_bHaveColumnNameIndex )
|
||||
BuildColumnNameIndex();
|
||||
|
||||
CRC32_t crc32;
|
||||
CRC32_Init( &crc32 );
|
||||
|
||||
FOR_EACH_MAP( m_MapIColumnInfo, iMapItem )
|
||||
{
|
||||
uint32 unColumn = m_MapIColumnInfo[iMapItem];
|
||||
CColumnInfo &columnInfo = m_VecColumnInfo[unColumn];
|
||||
// calculate checksum of all of our columns
|
||||
columnInfo.CalculateChecksum();
|
||||
int nChecksum = columnInfo.GetChecksum();
|
||||
CRC32_ProcessBuffer( &crc32, (void*) &nChecksum, sizeof( nChecksum ) );
|
||||
}
|
||||
|
||||
// keep checksum for entire record info
|
||||
CRC32_Final( &crc32 );
|
||||
m_nChecksum = crc32;
|
||||
m_bHaveChecksum = true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: add another index disallowing duplicates. If a duplicate item is
|
||||
// found, we'll set the flags on the new item from the existing one.
|
||||
//-----------------------------------------------------------------------------
|
||||
int CRecordInfo::AddIndex( const FieldSet_t& fieldSet )
|
||||
{
|
||||
for ( int n = 0; n < m_VecIndexes.Count(); n++ )
|
||||
{
|
||||
FieldSet_t& fs = m_VecIndexes[n];
|
||||
if ( FieldSet_t::CompareFieldSets( fieldSet, this, fs, this ) )
|
||||
{
|
||||
fs.SetClustered( fs.IsClustered() );
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int nRet = m_VecIndexes.AddToTail( fieldSet );
|
||||
return nRet;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns true if there is an IDENTITY column in the record info
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CRecordInfo::BHasIdentity() const
|
||||
{
|
||||
FOR_EACH_VEC( m_VecColumnInfo, nColumn)
|
||||
{
|
||||
if( m_VecColumnInfo[nColumn].BIsAutoIncrement() )
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CColumnInfo::CColumnInfo()
|
||||
{
|
||||
m_rgchName[0] = 0;
|
||||
m_nSQLColumn = 0;
|
||||
m_eType = k_EGCSQLTypeInvalid;
|
||||
m_nColFlags = 0;
|
||||
m_cubFixedSize = 0;
|
||||
m_cchMaxSize = 0;
|
||||
m_nChecksum = 0;
|
||||
m_bHaveChecksum = false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Sets column info for this column
|
||||
// Input: pchName - column name
|
||||
// nSQLColumn - column index in SQL to bind to (1-based)
|
||||
// eType - data type of column
|
||||
// cubFixedSize - for fixed-size fields, the size
|
||||
// nColFlags - attributes
|
||||
//-----------------------------------------------------------------------------
|
||||
void CColumnInfo::Set( const char *pchName, int nSQLColumn, EGCSQLType eType, int cubFixedSize, int nColFlags, int cchMaxSize )
|
||||
{
|
||||
Assert( !m_rgchName[0] );
|
||||
Q_strncpy( m_rgchName, pchName, Q_ARRAYSIZE( m_rgchName ) );
|
||||
m_nSQLColumn = nSQLColumn;
|
||||
m_eType = eType;
|
||||
|
||||
m_nColFlags = nColFlags;
|
||||
ValidateColFlags();
|
||||
|
||||
if ( !BIsVariableLength() )
|
||||
{
|
||||
Assert( cubFixedSize > 0 );
|
||||
m_cubFixedSize = cubFixedSize;
|
||||
m_cchMaxSize = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// it's variable length, so we need a max length
|
||||
m_cchMaxSize = cchMaxSize;
|
||||
m_cubFixedSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: returns whether this column is variable length
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CColumnInfo::BIsVariableLength() const
|
||||
{
|
||||
return m_eType == k_EGCSQLType_Blob || m_eType == k_EGCSQLType_String || m_eType == k_EGCSQLType_Image;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: convert column flags to a visible representation
|
||||
//-----------------------------------------------------------------------------
|
||||
void CColumnInfo::GetColFlagDescription( char* pstrOut, int cubOutLength ) const
|
||||
{
|
||||
if ( m_nColFlags == 0 )
|
||||
Q_strncpy( pstrOut, "(none)", cubOutLength );
|
||||
else
|
||||
{
|
||||
pstrOut[0] = 0;
|
||||
if ( m_nColFlags & k_nColFlagIndexed )
|
||||
Q_strncat( pstrOut, "(Indexed)", cubOutLength );
|
||||
if ( m_nColFlags & k_nColFlagUnique )
|
||||
Q_strncat( pstrOut, "(Unique)", cubOutLength );
|
||||
if ( m_nColFlags & k_nColFlagPrimaryKey )
|
||||
Q_strncat( pstrOut, "(PrimaryKey)", cubOutLength );
|
||||
if ( m_nColFlags & k_nColFlagAutoIncrement )
|
||||
Q_strncat( pstrOut, "(AutoIncrement)", cubOutLength );
|
||||
if ( m_nColFlags & k_nColFlagClustered )
|
||||
Q_strncat( pstrOut, "(Clustered)", cubOutLength );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: sets column flag bits
|
||||
// Input: nColFlag - bits to set. (Other bits are not cleared.)
|
||||
//-----------------------------------------------------------------------------
|
||||
void CColumnInfo::SetColFlagBits( int nColFlag )
|
||||
{
|
||||
ValidateColFlags();
|
||||
m_nColFlags |= nColFlag; // set these bits
|
||||
ValidateColFlags();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Calculates the checksum for this column
|
||||
//-----------------------------------------------------------------------------
|
||||
void CColumnInfo::CalculateChecksum()
|
||||
{
|
||||
if ( m_bHaveChecksum )
|
||||
return;
|
||||
|
||||
// calculate checksum of this column for easy comparsion
|
||||
CRC32_t crc32;
|
||||
CRC32_Init( &crc32 );
|
||||
CRC32_ProcessBuffer( &crc32, (void*) m_rgchName, Q_strlen( m_rgchName ) );
|
||||
CRC32_ProcessBuffer( &crc32, (void*) &m_nColFlags, sizeof( m_nColFlags ) );
|
||||
CRC32_ProcessBuffer( &crc32, (void*) &m_eType, sizeof( m_eType ) );
|
||||
CRC32_ProcessBuffer( &crc32, (void*) &m_cubFixedSize, sizeof( m_cubFixedSize ) );
|
||||
CRC32_ProcessBuffer( &crc32, (void*) &m_cchMaxSize, sizeof( m_cchMaxSize ) );
|
||||
CRC32_Final( &crc32 );
|
||||
|
||||
m_nChecksum = crc32;
|
||||
m_bHaveChecksum = true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// determine if this CColumnInfo is the same as the referenced
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CColumnInfo::operator==( const CColumnInfo& refOther ) const
|
||||
{
|
||||
if ( m_eType != refOther.m_eType )
|
||||
return false;
|
||||
if ( m_cubFixedSize != refOther.m_cubFixedSize )
|
||||
return false;
|
||||
if ( m_cchMaxSize != refOther.m_cchMaxSize )
|
||||
return false;
|
||||
if ( m_nColFlags != refOther.m_nColFlags )
|
||||
return false;
|
||||
if ( 0 != Q_strcmp( m_rgchName, refOther.m_rgchName ) )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Validates that column flags are set in valid combinations
|
||||
//-----------------------------------------------------------------------------
|
||||
void CColumnInfo::ValidateColFlags() const
|
||||
{
|
||||
// Check that column flags follow rules about how columns get expressed in SQL
|
||||
|
||||
if ( m_nColFlags & k_nColFlagPrimaryKey )
|
||||
{
|
||||
// a primary key must also be unique and indexed
|
||||
Assert( m_nColFlags & k_nColFlagUnique );
|
||||
Assert( m_nColFlags & k_nColFlagIndexed );
|
||||
}
|
||||
|
||||
// a column with uniqueness constraint must also be indexed
|
||||
if ( m_nColFlags & k_nColFlagUnique )
|
||||
Assert( m_nColFlags & k_nColFlagIndexed );
|
||||
}
|
||||
|
||||
|
||||
CRecordInfo *CRecordInfo::Alloc()
|
||||
{
|
||||
CRecordInfo *pRecordInfo = sm_MemPoolRecordInfo.Alloc();
|
||||
|
||||
#ifdef _DEBUG
|
||||
AUTO_LOCK( sm_mutexMemPoolRecordInfo );
|
||||
sm_mapPMemPoolRecordInfo.Insert( pRecordInfo );
|
||||
#endif
|
||||
|
||||
return pRecordInfo;
|
||||
}
|
||||
|
||||
|
||||
void CRecordInfo::DestroyThis()
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
AUTO_LOCK( sm_mutexMemPoolRecordInfo );
|
||||
sm_mapPMemPoolRecordInfo.Remove( this );
|
||||
#endif
|
||||
|
||||
sm_MemPoolRecordInfo.Free( this );
|
||||
}
|
||||
|
||||
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
|
||||
|
||||
void CRecordInfo::ValidateStatics( CValidator &validator, const char *pchName )
|
||||
{
|
||||
VALIDATE_SCOPE_STATIC( "CRecordInfo class statics" );
|
||||
|
||||
ValidateObj( sm_MemPoolRecordInfo );
|
||||
|
||||
#ifdef _DEBUG
|
||||
AUTO_LOCK( sm_mutexMemPoolRecordInfo );
|
||||
ValidateObj( sm_mapPMemPoolRecordInfo );
|
||||
FOR_EACH_MAP_FAST( sm_mapPMemPoolRecordInfo, i )
|
||||
{
|
||||
sm_mapPMemPoolRecordInfo[i]->Validate( validator, "sm_mapPMemPoolRecordInfo[i]" );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void CRecordInfo::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
VALIDATE_SCOPE();
|
||||
|
||||
m_VecIndexes.Validate( validator, "m_VecIndexes" );
|
||||
|
||||
ValidateObj( m_VecFKData );
|
||||
FOR_EACH_VEC( m_VecFKData, i )
|
||||
{
|
||||
ValidateObj( m_VecFKData[i] );
|
||||
}
|
||||
|
||||
for ( int iIndex = 0; iIndex < m_VecIndexes.Count(); iIndex++ )
|
||||
{
|
||||
ValidateObj( m_VecIndexes[iIndex] );
|
||||
}
|
||||
ValidateObj( m_vecFTSFields );
|
||||
|
||||
ValidateObj( m_VecColumnInfo );
|
||||
FOR_EACH_VEC( m_VecColumnInfo, nColumn )
|
||||
{
|
||||
CColumnInfo &columnInfo = GetColumnInfo( nColumn );
|
||||
ValidateObj( columnInfo );
|
||||
}
|
||||
ValidateObj( m_MapIColumnInfo );
|
||||
}
|
||||
|
||||
void CColumnInfo::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
VALIDATE_SCOPE();
|
||||
|
||||
}
|
||||
|
||||
#endif // DBGFLAG_VALIDATE
|
||||
|
||||
} // namespace GCSDK
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,409 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
CSchemaFull g_SchemaFull;
|
||||
CSchemaFull & GSchemaFull()
|
||||
{
|
||||
return g_SchemaFull;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CSchemaFull::CSchemaFull()
|
||||
{
|
||||
m_pubScratchBuffer = NULL;
|
||||
m_cubScratchBuffer = 0;
|
||||
m_unCheckSum = 0;
|
||||
|
||||
m_mapFTSEnabled.SetLessFunc( DefLessFunc( enum ESchemaCatalog ) );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CSchemaFull::~CSchemaFull()
|
||||
{
|
||||
Uninit();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Call this after you've finished setting up the SchemaFull (either
|
||||
// by loading it or by in GenerateIntrinsic). It calculates our checksum
|
||||
// and allocates our scratch buffer.
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSchemaFull::FinishInit()
|
||||
{
|
||||
// Calculate our checksum
|
||||
m_unCheckSum = 0;
|
||||
|
||||
for ( int iSchema = 0; iSchema < m_VecSchema.Count(); iSchema++ )
|
||||
m_unCheckSum += m_VecSchema[iSchema].CalcChecksum();
|
||||
|
||||
// Allocate our scratch buffer
|
||||
Assert( NULL == m_pubScratchBuffer );
|
||||
// Include some slop for field IDs and sizes in a sparse record
|
||||
// 2k is way overkill but still no big deal
|
||||
m_cubScratchBuffer = k_cubRecordMax + 2048;
|
||||
m_pubScratchBuffer = ( uint8 * ) malloc( m_cubScratchBuffer );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Call this after you've finished setting up the SchemaFull (either
|
||||
// by loading it or by in GenerateIntrinsic). It calculates our checksum
|
||||
// and allocates our scratch buffer.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void CSchemaFull::SetITable( CSchema* pSchema, int iTable )
|
||||
{
|
||||
// make sure we don't have this schema anywhere already
|
||||
for ( int iSchema = 0; iSchema < m_VecSchema.Count(); iSchema++ )
|
||||
{
|
||||
if ( pSchema != &m_VecSchema[iSchema] )
|
||||
AssertFatalMsg( m_VecSchema[iSchema].GetITable() != iTable, "Duplicate iTable in schema definition.\n" );
|
||||
}
|
||||
|
||||
// set the pSchema object
|
||||
pSchema->SetITable( iTable );
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Uninits the schema. Need to call this explicitly before app shutdown
|
||||
// on static instances of this object, as the CSchema objects
|
||||
// point to memory in static memory pools which may destruct
|
||||
// before static instances of this object.
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSchemaFull::Uninit()
|
||||
{
|
||||
m_VecSchema.RemoveAll();
|
||||
if ( NULL != m_pubScratchBuffer )
|
||||
{
|
||||
free( m_pubScratchBuffer );
|
||||
m_pubScratchBuffer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the scratch buffer. It is large enough to handle any
|
||||
// record, sparse or otherwise
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
uint8* CSchemaFull::GetPubScratchBuffer( )
|
||||
{
|
||||
return m_pubScratchBuffer;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: This is used during the generation of our intrinsic schema. We've
|
||||
// added a new schema to ourselves, and we need to make sure that it
|
||||
// matches the corresponding C class.
|
||||
// Input: pSchema - Schema to check
|
||||
// cField - Number of fields the schema should contain.
|
||||
// cubRecord - Size of a record in the schema
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSchemaFull::CheckSchema( CSchema *pSchema, int cField, uint32 cubRecord )
|
||||
{
|
||||
// We generate our structures and our schema using macros that operate on the
|
||||
// same source. We check a couple of things to make sure that they're properly in sync.
|
||||
|
||||
// This will fail if the schema's definition specifies the wrong iTable
|
||||
if ( pSchema != &m_VecSchema[pSchema->GetITable()] )
|
||||
{
|
||||
EmitError( SPEW_SQL, "Table %s has a bad iTable\n", pSchema->GetPchName() );
|
||||
}
|
||||
|
||||
// This will fail if there are missing lines in the schema definition
|
||||
if ( pSchema->GetCField() != cField )
|
||||
{
|
||||
EmitError( SPEW_SQL, "Badly formed table %s (blank line in schema def?)\n", pSchema->GetPchName() );
|
||||
AssertFatal( false );
|
||||
}
|
||||
|
||||
// This is unlikely to fail. It indicates some kind of size mismatch (maybe a packing problem?)
|
||||
if ( pSchema->CubRecordFixed() != cubRecord )
|
||||
{
|
||||
// You may hit this if END_FIELDDATA_HAS_VAR_FIELDS is not used properly
|
||||
EmitError( SPEW_SQL, "Table %s has an inconsistent size (class = %d, schema = %d)\n",
|
||||
pSchema->GetPchName(), cubRecord, pSchema->CubRecordFixed() );
|
||||
AssertFatal( false );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Finds the table with a given name.
|
||||
// Input: pchName - Name of the table to search for
|
||||
// Output: Index of the matching table ( k_iTableNil if there isn't one)
|
||||
//-----------------------------------------------------------------------------
|
||||
int CSchemaFull::FindITable( const char *pchName )
|
||||
{
|
||||
for ( int iSchema = 0; iSchema < m_VecSchema.Count(); iSchema++ )
|
||||
{
|
||||
if ( 0 == Q_strcmp( pchName, m_VecSchema[iSchema].GetPchName() ) )
|
||||
return iSchema;
|
||||
}
|
||||
|
||||
return k_iTableNil;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Finds the table with a given iTable (iSchema)
|
||||
// Input: iTable -
|
||||
// Output: NULL or a const char * to the name (for temporary use only)
|
||||
//-----------------------------------------------------------------------------
|
||||
const char * CSchemaFull::PchTableFromITable( int iTable )
|
||||
{
|
||||
if ( iTable < 0 || iTable >= m_VecSchema.Count() )
|
||||
return NULL;
|
||||
else
|
||||
return m_VecSchema[ iTable ].GetPchName();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSchemaFull::AddFullTextCatalog( enum ESchemaCatalog eCatalog, const char *pstrCatalogName, int nFileGroup )
|
||||
{
|
||||
CFTSCatalogInfo info;
|
||||
info.m_eCatalog = eCatalog;
|
||||
info.m_nFileGroup = nFileGroup;
|
||||
info.m_pstrName = strdup(pstrCatalogName);
|
||||
|
||||
m_vecFTSCatalogs.AddToTail( info );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
int CSchemaFull::GetFTSCatalogByName( enum ESchemaCatalog eCatalog, const char *pstrCatalogName )
|
||||
{
|
||||
int nIndex = -1;
|
||||
FOR_EACH_VEC( m_vecFTSCatalogs, i )
|
||||
{
|
||||
CFTSCatalogInfo &refInfo = m_vecFTSCatalogs[ i ];
|
||||
if ( 0 == Q_stricmp( pstrCatalogName, refInfo.m_pstrName ) )
|
||||
{
|
||||
nIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nIndex;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: turn on FTS for the named schema catalog. Called by the
|
||||
// InitIntrinsic() function.
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSchemaFull::EnableFTS( enum ESchemaCatalog eCatalog )
|
||||
{
|
||||
// mark it enabled in the map
|
||||
m_mapFTSEnabled.Insert( eCatalog, true );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: is FTS enabled for the supplied schema catalog?
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSchemaFull::GetFTSEnabled( enum ESchemaCatalog eCatalog )
|
||||
{
|
||||
int iEntry = m_mapFTSEnabled.Find( eCatalog );
|
||||
if ( iEntry == m_mapFTSEnabled.InvalidIndex() )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Adds a schema conversion instruction (for use in converting from
|
||||
// a different SchemaFull to this one).
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSchemaFull::AddDeleteTable( const char *pchTableName )
|
||||
{
|
||||
DeleteTable_t &deleteTable = m_VecDeleteTable[m_VecDeleteTable.AddToTail()];
|
||||
Q_strncpy( deleteTable.m_rgchTableName, pchTableName, sizeof( deleteTable.m_rgchTableName ) );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Adds a schema conversion instruction (for use in converting from
|
||||
// a different SchemaFull to this one).
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSchemaFull::AddRenameTable( const char *pchTableNameOld, const char *pchTableNameNew )
|
||||
{
|
||||
RenameTable_t &renameTable = m_VecRenameTable[m_VecRenameTable.AddToTail()];
|
||||
Q_strncpy( renameTable.m_rgchTableNameOld, pchTableNameOld, sizeof( renameTable.m_rgchTableNameOld ) );
|
||||
renameTable.m_iTableDst = FindITable( pchTableNameNew );
|
||||
Assert( k_iTableNil != renameTable.m_iTableDst );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Adds a schema conversion instruction (for use in converting from
|
||||
// a different SchemaFull to this one).
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSchemaFull::AddDeleteField( const char *pchTableName, const char *pchFieldName )
|
||||
{
|
||||
int iSchema = FindITable( pchTableName );
|
||||
AssertFatal( k_iTableNil != iSchema );
|
||||
|
||||
m_VecSchema[iSchema].AddDeleteField( pchFieldName );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Adds a schema conversion instruction (for use in converting from
|
||||
// a different SchemaFull to this one).
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSchemaFull::AddRenameField( const char *pchTableName, const char *pchFieldNameOld, const char *pchFieldNameNew )
|
||||
{
|
||||
int iSchema = FindITable( pchTableName );
|
||||
AssertFatal( k_iTableNil != iSchema );
|
||||
|
||||
m_VecSchema[iSchema].AddRenameField( pchFieldNameOld, pchFieldNameNew );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Adds a schema conversion instruction (for use in converting from
|
||||
// a different SchemaFull to this one).
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSchemaFull::AddAlterField( const char *pchTableName, const char *pchFieldNameOld, const char *pchFieldnameNew, PfnAlterField_t pfnAlterField )
|
||||
{
|
||||
int iSchema = FindITable( pchTableName );
|
||||
AssertFatal( k_iTableNil != iSchema );
|
||||
|
||||
m_VecSchema[iSchema].AddAlterField( pchFieldNameOld, pchFieldnameNew, pfnAlterField );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Add a trigger to the desired schema
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSchemaFull::AddTrigger( ESchemaCatalog eCatalog, const char *pchTableName, const char *pchTriggerName, ETriggerType eTriggerType, const char *pchTriggerText )
|
||||
{
|
||||
CTriggerInfo trigger;
|
||||
trigger.m_eTriggerType = eTriggerType;
|
||||
trigger.m_eSchemaCatalog = eCatalog;
|
||||
Q_strncpy( trigger.m_szTriggerName, pchTriggerName, Q_ARRAYSIZE( trigger.m_szTriggerName ) );
|
||||
Q_strncpy( trigger.m_szTriggerTableName, pchTableName, Q_ARRAYSIZE( trigger.m_szTriggerTableName ) );
|
||||
trigger.m_strText = pchTriggerText;
|
||||
|
||||
// add it to our list
|
||||
m_VecTriggers.AddToTail( trigger );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Figures out how to map a table from another SchemaFull into us.
|
||||
// First we check our conversion instructions to see if any apply,
|
||||
// and then we look for a straightforward match.
|
||||
// Input: pchTableName - Name of the table we're trying to map
|
||||
// piTableDst - [Return] Index of the table to map it to
|
||||
// Output: true if we know what to do with this table (if false, the conversion
|
||||
// is undefined and dangerous).
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSchemaFull::BCanConvertTable( const char *pchTableName, int *piTableDst )
|
||||
{
|
||||
// Should this table be deleted?
|
||||
for ( int iDeleteTable = 0; iDeleteTable < m_VecDeleteTable.Count(); iDeleteTable++ )
|
||||
{
|
||||
if ( 0 == Q_strcmp( pchTableName, m_VecDeleteTable[iDeleteTable].m_rgchTableName ) )
|
||||
{
|
||||
*piTableDst = k_iTableNil;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Should this table be renamed?
|
||||
for ( int iRenameTable = 0; iRenameTable < m_VecRenameTable.Count(); iRenameTable++ )
|
||||
{
|
||||
if ( 0 == Q_strcmp( pchTableName, m_VecRenameTable[iRenameTable].m_rgchTableNameOld ) )
|
||||
{
|
||||
*piTableDst = m_VecRenameTable[iRenameTable].m_iTableDst;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Find out which of our tables this table maps to (if it doesn't map
|
||||
// to any of them, we don't know what to do with it).
|
||||
*piTableDst = FindITable( pchTableName );
|
||||
return ( k_iTableNil != *piTableDst );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets the default SQL schema name for a catalog
|
||||
//-----------------------------------------------------------------------------
|
||||
const char *CSchemaFull::GetDefaultSchemaNameForCatalog( ESchemaCatalog eCatalog )
|
||||
{
|
||||
// For all catalogs it's actually the same
|
||||
if ( m_strDefaultSchemaName.IsEmpty() )
|
||||
{
|
||||
m_strDefaultSchemaName.Set( CFmtStr( "App%u", GGCBase()->GetAppID() ) );
|
||||
}
|
||||
|
||||
return m_strDefaultSchemaName.Get();
|
||||
}
|
||||
|
||||
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Run a global validation pass on all of our data structures and memory
|
||||
// allocations.
|
||||
// Input: validator - Our global validator object
|
||||
// pchName - Our name (typically a member var in our container)
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSchemaFull::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
VALIDATE_SCOPE();
|
||||
|
||||
ValidateObj( m_VecSchema );
|
||||
for ( int iSchema = 0; iSchema < m_VecSchema.Count(); iSchema++ )
|
||||
{
|
||||
ValidateObj( m_VecSchema[iSchema] );
|
||||
}
|
||||
|
||||
ValidateObj( m_VecDeleteTable );
|
||||
ValidateObj( m_VecRenameTable );
|
||||
|
||||
ValidateObj( m_mapFTSEnabled );
|
||||
|
||||
ValidateObj( m_vecFTSCatalogs );
|
||||
FOR_EACH_VEC( m_vecFTSCatalogs, i )
|
||||
{
|
||||
ValidateObj( m_vecFTSCatalogs[i] );
|
||||
}
|
||||
|
||||
ValidateObj( m_VecTriggers );
|
||||
FOR_EACH_VEC( m_VecTriggers, i )
|
||||
{
|
||||
ValidateObj( m_VecTriggers[i] );
|
||||
}
|
||||
|
||||
validator.ClaimMemory( m_pubScratchBuffer );
|
||||
}
|
||||
#endif // DBGFLAG_VALIDATE
|
||||
|
||||
} // namespace GCSDK
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,953 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Provides access to SQL at a high level
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "gcsdk/sqlaccess/sqlaccess.h"
|
||||
#include "gcsdk/gcsqlquery.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
template< typename LISTENER_FUNC >
|
||||
static void RunAndClearListenerList( std::vector< LISTENER_FUNC > &vecListeners )
|
||||
{
|
||||
// Let us not underestimate the ability of random listeners to re-enter everything.
|
||||
std::vector< LISTENER_FUNC > listenerCopy;
|
||||
listenerCopy.swap( vecListeners );
|
||||
vecListeners.clear();
|
||||
|
||||
// Why would you consider such a thing
|
||||
DO_NOT_YIELD_THIS_SCOPE();
|
||||
|
||||
for ( const auto &listener : listenerCopy )
|
||||
{
|
||||
listener();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//------------------------------------------------------------------------------------
|
||||
CSQLAccess::CSQLAccess( ESchemaCatalog eSchemaCatalog )
|
||||
: m_eSchemaCatalog( eSchemaCatalog)
|
||||
, m_pCurrentQuery( NULL )
|
||||
, m_bInTransaction( false )
|
||||
{
|
||||
m_pQueryGroup = CGCSQLQueryGroup::Alloc();
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//------------------------------------------------------------------------------------
|
||||
CSQLAccess::~CSQLAccess( )
|
||||
{
|
||||
SAFE_RELEASE( m_pQueryGroup );
|
||||
Assert( !m_pCurrentQuery );
|
||||
SAFE_DELETE( m_pCurrentQuery );
|
||||
AssertMsg( !m_bInTransaction, "GCSDK::CSQLAccess object being destroyed with a transaction pending. Use BCommitTransaction or RollbackTransaction to match your BBeginTransaction call." );
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Perform a query
|
||||
//------------------------------------------------------------------------------------
|
||||
bool CSQLAccess::BYieldingExecute( const char *pchName, const char *pchSQLCommand, uint32 *pcRowsAffected, bool bSpewOnError )
|
||||
{
|
||||
if ( NULL == pchName )
|
||||
{
|
||||
pchName = pchSQLCommand;
|
||||
}
|
||||
|
||||
bool bStandalone = !BInTransaction();
|
||||
if( bStandalone )
|
||||
{
|
||||
BBeginTransaction( pchName );
|
||||
}
|
||||
|
||||
CurrentQuery()->SetCommand( pchSQLCommand );
|
||||
m_pQueryGroup->AddQuery( m_pCurrentQuery );
|
||||
m_pCurrentQuery = NULL;
|
||||
|
||||
bool bSuccess = true;
|
||||
if( bStandalone )
|
||||
{
|
||||
bSuccess = BCommitTransaction();
|
||||
if( bSuccess && pcRowsAffected )
|
||||
{
|
||||
*pcRowsAffected = m_pQueryGroup->GetResults()->GetRowsAffected( 0 );
|
||||
}
|
||||
}
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Starts a transaction
|
||||
//------------------------------------------------------------------------------------
|
||||
bool CSQLAccess::BBeginTransaction( const char *pchName )
|
||||
{
|
||||
Assert( !m_bInTransaction );
|
||||
if( m_bInTransaction )
|
||||
return false;
|
||||
m_pQueryGroup->Clear();
|
||||
m_pQueryGroup->SetName( pchName );
|
||||
m_bInTransaction = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Returns the string last passed to BBeginTransaction
|
||||
//------------------------------------------------------------------------------------
|
||||
const char *CSQLAccess::PchTransactionName( ) const
|
||||
{
|
||||
return m_pQueryGroup->PchName();
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Commits a transaction to the database
|
||||
//------------------------------------------------------------------------------------
|
||||
bool CSQLAccess::BCommitTransaction( bool bAllowEmpty )
|
||||
{
|
||||
Assert( BInTransaction() );
|
||||
if( !BInTransaction() )
|
||||
return false;
|
||||
|
||||
if( !m_pCurrentQuery && !m_pQueryGroup->GetStatementCount() )
|
||||
{
|
||||
if( bAllowEmpty )
|
||||
{
|
||||
// No-op success
|
||||
m_bInTransaction = false;
|
||||
RunListeners_Commit();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertMsg1( false, "BCommitTransaction with empty transaction at %s", m_pQueryGroup->PchName() );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AssertMsg1( !m_pCurrentQuery, "Unexecuted query present in BCommitTransaction: %s", m_pCurrentQuery->PchCommand() );
|
||||
if( m_pCurrentQuery )
|
||||
return false;
|
||||
|
||||
m_bInTransaction = false;
|
||||
|
||||
if( !GJobCur().BYieldingRunQuery( m_pQueryGroup, m_eSchemaCatalog ) )
|
||||
{
|
||||
// Notify listeners that the transaction did not succeed
|
||||
RunListeners_Rollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
// The transaction presumably did make the database, so we do not notify rollback listeners beyond here.
|
||||
RunListeners_Commit();
|
||||
|
||||
if( !m_pQueryGroup->GetResults() )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Rolls back a transaction and clears any queries
|
||||
//------------------------------------------------------------------------------------
|
||||
void CSQLAccess::RollbackTransaction()
|
||||
{
|
||||
bool bWasTransaction = BInTransaction();
|
||||
|
||||
Assert( bWasTransaction );
|
||||
SAFE_DELETE( m_pCurrentQuery );
|
||||
m_bInTransaction = false;
|
||||
|
||||
if ( bWasTransaction )
|
||||
{
|
||||
RunListeners_Rollback();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_vecCommitListeners.clear();
|
||||
m_vecRollbackListeners.clear();
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Adds a listener to be called synchronously should the transaction successfully commit
|
||||
//------------------------------------------------------------------------------------
|
||||
void CSQLAccess::AddCommitListener( std::function<void (void)> &&listener )
|
||||
{
|
||||
if ( !BInTransaction() )
|
||||
{
|
||||
AssertMsg( BInTransaction(), "Adding a listener to a non-transaction access, will never fire" );
|
||||
return;
|
||||
}
|
||||
|
||||
m_vecCommitListeners.push_back( std::move( listener ) );
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Adds a listener to be called synchronously should the transaction fail or explicitly rollback
|
||||
//------------------------------------------------------------------------------------
|
||||
void CSQLAccess::AddRollbackListener( std::function<void (void)> &&listener )
|
||||
{
|
||||
if ( !BInTransaction() )
|
||||
{
|
||||
AssertMsg( BInTransaction(), "Adding a listener to a non-transaction access, will never fire" );
|
||||
return;
|
||||
}
|
||||
|
||||
m_vecRollbackListeners.push_back( std::move( listener ) );
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Notifies listeners of successful commit.
|
||||
//------------------------------------------------------------------------------------
|
||||
void CSQLAccess::RunListeners_Commit()
|
||||
{
|
||||
RunAndClearListenerList( m_vecCommitListeners );
|
||||
// Clear the unused set
|
||||
m_vecRollbackListeners.clear();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Notifies listeners of a implicitly or explicitly rolled back transactions and clears the listener list.
|
||||
//------------------------------------------------------------------------------------
|
||||
void CSQLAccess::RunListeners_Rollback()
|
||||
{
|
||||
RunAndClearListenerList( m_vecRollbackListeners );
|
||||
// Clear the unused set
|
||||
m_vecCommitListeners.clear();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Perform a query that returns a single string
|
||||
//------------------------------------------------------------------------------------
|
||||
CSQLAccess::EReadSingleResultResult CSQLAccess::BYieldingExecuteSingleResultDataInternal( const char *pchName, const char *pchSQLCommand, EGCSQLType eType, uint8 **ppubData, uint32 *punSize, uint32 *pcRowsAffected, bool bHasDefaultValue )
|
||||
{
|
||||
AssertMsg( !BInTransaction(), "BYieldingExecuteSingleResultData is not supported in a transaction" );
|
||||
if( BInTransaction() )
|
||||
return eReadSingle_Error;
|
||||
|
||||
bool bRet = BYieldingExecute( pchName, pchSQLCommand, pcRowsAffected );
|
||||
if ( !bRet )
|
||||
return eReadSingle_Error;
|
||||
|
||||
if( m_pQueryGroup->GetResults()->GetResultSetCount() != 1 )
|
||||
{
|
||||
AssertMsg1( false, "Expected single result set, found %d", m_pQueryGroup->GetResults()->GetResultSetCount() );
|
||||
return eReadSingle_Error;
|
||||
}
|
||||
|
||||
IGCSQLResultSet *pResultSet = m_pQueryGroup->GetResults()->GetResultSet( 0 );
|
||||
|
||||
// If we have a default value, getting back zero rows is acceptable.
|
||||
if( pResultSet->GetRowCount() == 0 && bHasDefaultValue )
|
||||
{
|
||||
return eReadSingle_UseDefault;
|
||||
}
|
||||
|
||||
// If we either have more than one row or no default value specified, that's an error.
|
||||
if( pResultSet->GetRowCount() != 1 )
|
||||
{
|
||||
AssertMsg1( false, "Expected single result, found %d", pResultSet->GetRowCount() );
|
||||
return eReadSingle_Error;
|
||||
}
|
||||
|
||||
if( pResultSet->GetColumnCount() != 1 )
|
||||
{
|
||||
AssertMsg1( false, "Expected single column, found %d", pResultSet->GetColumnCount() );
|
||||
return eReadSingle_Error;
|
||||
}
|
||||
if( pResultSet->GetColumnType( 0 ) != eType )
|
||||
{
|
||||
AssertMsg2( false, "Expected column of type %s, found %s", PchNameFromEGCSQLType( eType ), PchNameFromEGCSQLType( pResultSet->GetColumnType( 0 ) ) );
|
||||
return eReadSingle_Error;
|
||||
}
|
||||
|
||||
return pResultSet->GetData( 0, 0, ppubData, punSize )
|
||||
? eReadSingle_ResultFound
|
||||
: eReadSingle_Error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Perform a query that returns a single string
|
||||
//------------------------------------------------------------------------------------
|
||||
bool CSQLAccess::BYieldingExecuteString( const char *pchName, const char *pchSQLCommand, CFmtStr1024 *psResult, uint32 *pcRowsAffected )
|
||||
{
|
||||
uint8 *pubData;
|
||||
uint32 cubData;
|
||||
if( CSQLAccess::BYieldingExecuteSingleResultDataInternal( pchName, pchSQLCommand, k_EGCSQLType_String, &pubData, &cubData, pcRowsAffected, false ) != eReadSingle_ResultFound )
|
||||
return false;
|
||||
|
||||
*psResult = (char *)pubData;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Perform a query that returns a single int
|
||||
//------------------------------------------------------------------------------------
|
||||
bool CSQLAccess::BYieldingExecuteScalarInt( const char *pchName, const char *pchSQLCommand, int *pnResult, uint32 *pcRowsAffected )
|
||||
{
|
||||
return BYieldingExecuteSingleResult<int32, uint32>( pchName, pchSQLCommand, k_EGCSQLType_int32, pnResult, pcRowsAffected );
|
||||
}
|
||||
|
||||
bool CSQLAccess::BYieldingExecuteScalarIntWithDefault( const char *pchName, const char *pchSQLCommand, int *pnResult, int iDefaultValue, uint32 *pcRowsAffected )
|
||||
{
|
||||
return BYieldingExecuteSingleResultWithDefault<int32, uint32>( pchName, pchSQLCommand, k_EGCSQLType_int32, pnResult, iDefaultValue, pcRowsAffected );
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Perform a query that returns a single uint32
|
||||
//------------------------------------------------------------------------------------
|
||||
bool CSQLAccess::BYieldingExecuteScalarUint32( const char *pchName, const char *pchSQLCommand, uint32 *punResult, uint32 *pcRowsAffected )
|
||||
{
|
||||
return BYieldingExecuteSingleResult<uint32, uint32>( pchName, pchSQLCommand, k_EGCSQLType_int32, punResult, pcRowsAffected );
|
||||
}
|
||||
|
||||
bool CSQLAccess::BYieldingExecuteScalarUint32WithDefault( const char *pchName, const char *pchSQLCommand, uint32 *punResult, uint32 unDefaultValue, uint32 *pcRowsAffected )
|
||||
{
|
||||
return BYieldingExecuteSingleResultWithDefault<uint32, uint32>( pchName, pchSQLCommand, k_EGCSQLType_int32, punResult, unDefaultValue, pcRowsAffected );
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: A bunch of pass throughs to the query itself
|
||||
//------------------------------------------------------------------------------------
|
||||
void CSQLAccess::AddBindParam( const char *pchValue )
|
||||
{
|
||||
CurrentQuery()->AddBindParam( pchValue );
|
||||
}
|
||||
|
||||
void CSQLAccess::AddBindParam( const int16 nValue )
|
||||
{
|
||||
CurrentQuery()->AddBindParam( nValue );
|
||||
}
|
||||
|
||||
void CSQLAccess::AddBindParam( const uint16 uValue )
|
||||
{
|
||||
CurrentQuery()->AddBindParam( uValue );
|
||||
}
|
||||
|
||||
void CSQLAccess::AddBindParam( const int32 nValue )
|
||||
{
|
||||
CurrentQuery()->AddBindParam( nValue );
|
||||
}
|
||||
|
||||
void CSQLAccess::AddBindParam( const uint32 uValue )
|
||||
{
|
||||
CurrentQuery()->AddBindParam( uValue );
|
||||
}
|
||||
|
||||
void CSQLAccess::AddBindParam( const uint64 ulValue )
|
||||
{
|
||||
CurrentQuery()->AddBindParam( ulValue );
|
||||
}
|
||||
|
||||
void CSQLAccess::AddBindParam( const uint8 *ubValue, const int cubValue )
|
||||
{
|
||||
CurrentQuery()->AddBindParam( ubValue, cubValue );
|
||||
}
|
||||
|
||||
void CSQLAccess::AddBindParam( const float fValue )
|
||||
{
|
||||
CurrentQuery()->AddBindParam( fValue );
|
||||
}
|
||||
|
||||
void CSQLAccess::AddBindParam( const double dValue )
|
||||
{
|
||||
CurrentQuery()->AddBindParam( dValue );
|
||||
}
|
||||
|
||||
void CSQLAccess::AddBindParamRaw( EGCSQLType eType, const byte *pubData, uint32 cubData )
|
||||
{
|
||||
CurrentQuery()->AddBindParamRaw( eType, pubData, cubData );
|
||||
}
|
||||
|
||||
void CSQLAccess::ClearParams()
|
||||
{
|
||||
if( m_pCurrentQuery )
|
||||
{
|
||||
delete m_pCurrentQuery;
|
||||
m_pCurrentQuery = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
IGCSQLResultSetList *CSQLAccess::GetResults()
|
||||
{
|
||||
return m_pQueryGroup->GetResults();
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Returns the number of result sets
|
||||
//------------------------------------------------------------------------------------
|
||||
uint32 CSQLAccess::GetResultSetCount()
|
||||
{
|
||||
if( m_pQueryGroup->GetResults() )
|
||||
return m_pQueryGroup->GetResults()->GetResultSetCount();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Returns the number of rows in a result set
|
||||
//------------------------------------------------------------------------------------
|
||||
uint32 CSQLAccess::GetResultSetRowCount( uint32 unResultSet )
|
||||
{
|
||||
if( m_pQueryGroup->GetResults() && unResultSet < m_pQueryGroup->GetResults()->GetResultSetCount() )
|
||||
return m_pQueryGroup->GetResults()->GetResultSet( unResultSet )->GetRowCount();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Purpose: Returns a CSQLRecord object that represents a row in a result set
|
||||
//------------------------------------------------------------------------------------
|
||||
CSQLRecord CSQLAccess::GetResultRecord( uint32 unResultSet, uint32 unRow )
|
||||
{
|
||||
if( m_pQueryGroup->GetResults() && unResultSet < m_pQueryGroup->GetResults()->GetResultSetCount() )
|
||||
{
|
||||
IGCSQLResultSet *pResultSet = m_pQueryGroup->GetResults()->GetResultSet( unResultSet );
|
||||
if( unRow < pResultSet->GetRowCount() )
|
||||
return CSQLRecord( unRow, pResultSet );
|
||||
}
|
||||
return CSQLRecord(); // if there was a problem return an empty record
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Inserts a new record into the DS
|
||||
// Input: pRecordBase - record to insert
|
||||
// Output: true if successful, false otherwise
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLAccess::BYieldingInsertRecord( const CRecordBase *pRecordBase )
|
||||
{
|
||||
ClearParams();
|
||||
|
||||
const CRecordInfo *pRecordInfo = pRecordBase->GetPSchema()->GetRecordInfo();
|
||||
int cColumns = pRecordInfo->GetNumColumns();
|
||||
for ( int nColumn = 0; nColumn < cColumns; nColumn++ )
|
||||
{
|
||||
const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( nColumn );
|
||||
if ( !columnInfo.BIsInsertable() )
|
||||
continue;
|
||||
|
||||
uint8 *pubData;
|
||||
uint32 cubData;
|
||||
DbgVerify( pRecordBase->BGetField( nColumn, &pubData, &cubData ) );
|
||||
|
||||
CurrentQuery()->AddBindParamRaw( columnInfo.GetType(), pubData, cubData );
|
||||
}
|
||||
|
||||
uint32 nRows;
|
||||
const char *pchStatement = pRecordBase->GetPSchema()->GetInsertStatementText();
|
||||
|
||||
bool bRet = BYieldingExecute( pchStatement, pchStatement, &nRows );
|
||||
return ( nRows == 1 || BInTransaction() ) && bRet;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Inserts a new record into the DS if such row doesn't exist
|
||||
// Input: pRecordBase - record to insert
|
||||
// Output: true if successful, false otherwise
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLAccess::BYieldingInsertWhenNotMatchedOnPK( CRecordBase *pRecordBase )
|
||||
{
|
||||
ClearParams();
|
||||
|
||||
const CRecordInfo *pRecordInfo = pRecordBase->GetPSchema()->GetRecordInfo();
|
||||
int cColumns = pRecordInfo->GetNumColumns();
|
||||
for ( int nColumn = 0; nColumn < cColumns; nColumn++ )
|
||||
{
|
||||
const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( nColumn );
|
||||
if ( !columnInfo.BIsInsertable() )
|
||||
{
|
||||
Assert( columnInfo.BIsInsertable() );
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8 *pubData;
|
||||
uint32 cubData;
|
||||
DbgVerify( pRecordBase->BGetField( nColumn, &pubData, &cubData ) );
|
||||
|
||||
CurrentQuery()->AddBindParamRaw( columnInfo.GetType(), pubData, cubData );
|
||||
}
|
||||
|
||||
uint32 nRows;
|
||||
const char *pchStatement = pRecordBase->GetPSchema()->GetMergeStatementTextOnPKWhenNotMatchedInsert();
|
||||
|
||||
bool bRet = BYieldingExecute( pchStatement, pchStatement, &nRows );
|
||||
return ( nRows == 1 || nRows == 0 || BInTransaction() ) && bRet;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Inserts a new record into the DS if such row doesn't exist
|
||||
// updates an existing row if such row is matched by PK
|
||||
// Input: pRecordBase - record to insert
|
||||
// Output: true if successful, false otherwise
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLAccess::BYieldingInsertOrUpdateOnPK( CRecordBase *pRecordBase )
|
||||
{
|
||||
ClearParams();
|
||||
|
||||
const CRecordInfo *pRecordInfo = pRecordBase->GetPSchema()->GetRecordInfo();
|
||||
int cColumns = pRecordInfo->GetNumColumns();
|
||||
for ( int nColumn = 0; nColumn < cColumns; nColumn++ )
|
||||
{
|
||||
const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( nColumn );
|
||||
if ( !columnInfo.BIsInsertable() )
|
||||
{
|
||||
Assert( columnInfo.BIsInsertable() );
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8 *pubData;
|
||||
uint32 cubData;
|
||||
DbgVerify( pRecordBase->BGetField( nColumn, &pubData, &cubData ) );
|
||||
|
||||
CurrentQuery()->AddBindParamRaw( columnInfo.GetType(), pubData, cubData );
|
||||
}
|
||||
|
||||
uint32 nRows;
|
||||
const char *pchStatement = pRecordBase->GetPSchema()->GetMergeStatementTextOnPKWhenMatchedUpdateWhenNotMatchedInsert();
|
||||
|
||||
bool bRet = BYieldingExecute( pchStatement, pchStatement, &nRows );
|
||||
return ( nRows == 1 || BInTransaction() ) && bRet;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Inserts a new record into the DB and reads non-insertable fields back
|
||||
// into the record.
|
||||
// Input: pRecordBase - record to insert
|
||||
// Output: true if successful, false otherwise
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLAccess::BYieldingInsertWithIdentity( CRecordBase* pRecordBase )
|
||||
{
|
||||
AssertMsg( !BInTransaction(), "BYieldingInsertWithIdentity is not supported in a transaction" );
|
||||
if( BInTransaction() )
|
||||
return false;
|
||||
ClearParams();
|
||||
|
||||
TSQLCmdStr sStatement;
|
||||
CUtlVector<int> vecOutputFields;
|
||||
CRecordInfo *pRecordInfo = pRecordBase->GetPSchema()->GetRecordInfo();
|
||||
BuildInsertAndReadStatementText( &sStatement, &vecOutputFields, pRecordInfo );
|
||||
|
||||
AssertMsg( vecOutputFields.Count() > 0, "BYieldingInsertAndReadRecord called for a record type with no non-insertable columns" );
|
||||
if ( vecOutputFields.Count() == 0 )
|
||||
return false;
|
||||
|
||||
int cColumns = pRecordInfo->GetNumColumns();
|
||||
for ( int nColumn = 0; nColumn < cColumns; nColumn++ )
|
||||
{
|
||||
const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( nColumn );
|
||||
if ( !columnInfo.BIsInsertable() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8 *pubData;
|
||||
uint32 cubData;
|
||||
DbgVerify( pRecordBase->BGetField( nColumn, &pubData, &cubData ) );
|
||||
|
||||
CurrentQuery()->AddBindParamRaw( columnInfo.GetType(), pubData, cubData );
|
||||
}
|
||||
|
||||
bool bRet = BYieldingExecute( sStatement, sStatement );
|
||||
if( !bRet )
|
||||
return false;
|
||||
|
||||
Assert( 1 == GetResultSetCount() );
|
||||
if ( 1 != GetResultSetCount() )
|
||||
return false;
|
||||
|
||||
IGCSQLResultSet *pResultSet = m_pQueryGroup->GetResults()->GetResultSet( 0 );
|
||||
Assert( 1 == pResultSet->GetRowCount() );
|
||||
if ( 1 != pResultSet->GetRowCount() )
|
||||
return false;
|
||||
|
||||
Assert( (uint32)vecOutputFields.Count() == pResultSet->GetColumnCount() );
|
||||
if ( (uint32)vecOutputFields.Count() != pResultSet->GetColumnCount() )
|
||||
return false;
|
||||
|
||||
for( uint32 nColumn = 0; nColumn < pResultSet->GetColumnCount(); nColumn++ )
|
||||
{
|
||||
uint8 *pubData;
|
||||
uint32 cubData;
|
||||
DbgVerify( pResultSet->GetData( 0, nColumn, &pubData, &cubData ) );
|
||||
|
||||
int nSchColumn = vecOutputFields[nColumn];
|
||||
Assert( pResultSet->GetColumnType( nColumn ) == pRecordInfo->GetColumnInfo( nSchColumn ).GetType() );
|
||||
DbgVerify( pRecordBase->BSetField( nSchColumn, pubData, cubData ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Reads a list of records from the DB according to the specified where
|
||||
// clause
|
||||
// Input: pRecordBase - record to read
|
||||
// readSet - The set of columns to read
|
||||
// whereSet - The set of columns to query on
|
||||
// Output: true if successful, false otherwise
|
||||
//-----------------------------------------------------------------------------
|
||||
EResult CSQLAccess::YieldingReadRecordWithWhereColumns( CRecordBase *pRecord, const CColumnSet & readSet, const CColumnSet & whereSet, const char* pchOrderClause )
|
||||
{
|
||||
AssertMsg( !BInTransaction(), "BYieldingReadRecordWithWhereColumns is not supported in a transaction" );
|
||||
if( BInTransaction() )
|
||||
return k_EResultInvalidState;
|
||||
|
||||
//if there is an order by clause, only take the top one, if there isn't, then validate that we have a single instance
|
||||
const char* pszTopClause = ( pchOrderClause ) ? "TOP (1)" : "TOP (2)";
|
||||
|
||||
TSQLCmdStr sStatement;
|
||||
BuildSelectStatementText( &sStatement, readSet, pszTopClause );
|
||||
|
||||
// if we actually have some columns for the where clause,
|
||||
// append a where clause.
|
||||
if( whereSet.GetColumnCount() )
|
||||
{
|
||||
sStatement.Append( " WHERE " );
|
||||
AppendWhereClauseText( &sStatement, whereSet );
|
||||
AddRecordParameters( *pRecord, whereSet );
|
||||
}
|
||||
//append the order by if they added one
|
||||
if( pchOrderClause )
|
||||
{
|
||||
sStatement.Append( " ORDER BY " );
|
||||
sStatement.Append( pchOrderClause );
|
||||
}
|
||||
|
||||
Assert(!readSet.IsEmpty() );
|
||||
if( !BYieldingExecute( sStatement, sStatement ) )
|
||||
return k_EResultFail;
|
||||
|
||||
if ( GetResultSetCount() != 1 )
|
||||
{
|
||||
AssertMsg( GetResultSetCount() == 1, "Unexpected number of result sets returned from select statement" );
|
||||
return k_EResultFail;
|
||||
}
|
||||
|
||||
// make sure the types are the same
|
||||
IGCSQLResultSet *pResultSet = m_pQueryGroup->GetResults()->GetResultSet( 0 );
|
||||
if ( pResultSet->GetRowCount() == 0 )
|
||||
return k_EResultNoMatch;
|
||||
|
||||
//note that since we only take the top one when there is an order by clause, we don't need to handle that case down here, only if top 2 is selected
|
||||
if( pResultSet->GetRowCount() != 1 )
|
||||
{
|
||||
// Make sure we aren't failing because there are multiple matching records.
|
||||
// That is probably a misuse of the API or some unexpected condition.
|
||||
AssertMsg1( false, "BYieldingReadRecordWithWhereColumns from %s failing because multiple records match WHERE clause", readSet.GetRecordInfo()->GetName() );
|
||||
return k_EResultLimitExceeded;
|
||||
}
|
||||
FOR_EACH_COLUMN_IN_SET( readSet, nColumnIndex )
|
||||
{
|
||||
EGCSQLType eRecordType = readSet.GetColumnInfo( nColumnIndex ).GetType();
|
||||
EGCSQLType eResultType = pResultSet->GetColumnType( nColumnIndex );
|
||||
|
||||
AssertMsg2( eResultType == eRecordType, "Column %d type mismatch in %s", nColumnIndex, readSet.GetRecordInfo()->GetName() );
|
||||
if( eRecordType != eResultType )
|
||||
return k_EResultInvalidParam;
|
||||
}
|
||||
|
||||
CSQLRecord sqlRecord = GetResultRecord( 0, 0 );
|
||||
|
||||
FOR_EACH_COLUMN_IN_SET( readSet, nColumnIndex )
|
||||
{
|
||||
uint8 *pubData;
|
||||
uint32 cubData;
|
||||
|
||||
DbgVerify( sqlRecord.BGetColumnData( nColumnIndex, &pubData, (int*)&cubData ) );
|
||||
DbgVerify( pRecord->BSetField( readSet.GetColumn( nColumnIndex), pubData, cubData ) );
|
||||
}
|
||||
|
||||
return k_EResultOK;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Updates a record in the DB
|
||||
// Input: record - data source for columns to match against (whereColumns) and
|
||||
// columns to assign (updateColumns)
|
||||
// whereColumns - columns to match against
|
||||
// updateColumns - columns to update
|
||||
// Output: true if successful, false otherwise
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLAccess::BYieldingUpdateRecord( const CRecordBase & record, const CColumnSet & whereColumns, const CColumnSet & updateColumns, const CSQLOutputParams *pOptionalOutputParams /* = NULL */ )
|
||||
{
|
||||
return BYieldingUpdateRecords( record, whereColumns, record, updateColumns, pOptionalOutputParams );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLAccess::BYieldingUpdateRecords( const CRecordBase & whereRecord, const CColumnSet & whereColumns, const CRecordBase & updateRecord, const CColumnSet & updateColumns, const CSQLOutputParams *pOptionalOutputParams /* = NULL */ )
|
||||
{
|
||||
ClearParams();
|
||||
|
||||
Assert( whereColumns.GetRecordInfo() == updateColumns.GetRecordInfo() );
|
||||
if ( whereColumns.GetRecordInfo() != updateColumns.GetRecordInfo() )
|
||||
return false;
|
||||
Assert( whereColumns.GetRecordInfo() == whereRecord.GetPSchema()->GetRecordInfo() );
|
||||
if ( whereColumns.GetRecordInfo() != whereRecord.GetPSchema()->GetRecordInfo() )
|
||||
return false;
|
||||
Assert( whereColumns.GetRecordInfo() == updateRecord.GetPSchema()->GetRecordInfo() );
|
||||
if ( whereColumns.GetRecordInfo() != updateRecord.GetPSchema()->GetRecordInfo() )
|
||||
return false;
|
||||
|
||||
AssertMsg( !updateColumns.IsEmpty(), "Someone is calling BYieldingUpdateRecord with no columns to update." );
|
||||
if ( updateColumns.IsEmpty() )
|
||||
return false;
|
||||
|
||||
// add the columns we're updating as bound params
|
||||
TSQLCmdStr sStatement;
|
||||
BuildUpdateStatementText( &sStatement, updateColumns );
|
||||
|
||||
AddRecordParameters( updateRecord, updateColumns );
|
||||
|
||||
// did the users specify an OUTPUT block?
|
||||
if ( pOptionalOutputParams )
|
||||
{
|
||||
TSQLCmdStr sOutput;
|
||||
BuildOutputClauseText( &sOutput, pOptionalOutputParams->GetColumnSet() );
|
||||
sStatement.Append( sOutput );
|
||||
|
||||
AddRecordParameters( pOptionalOutputParams->GetRecord(), pOptionalOutputParams->GetColumnSet() );
|
||||
}
|
||||
|
||||
if ( !whereColumns.IsEmpty() )
|
||||
{
|
||||
sStatement.Append( " WHERE " );
|
||||
AppendWhereClauseText( &sStatement, whereColumns );
|
||||
|
||||
// add the columns we're querying on as bound params
|
||||
AddRecordParameters( whereRecord, whereColumns );
|
||||
}
|
||||
|
||||
return BYieldingExecute( sStatement, sStatement );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Deletes this record's row in the table
|
||||
// Input: record - record to delete
|
||||
// whereColumns - columns to use when searching for this record
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLAccess::BYieldingDeleteRecords( const CRecordBase & record, const CColumnSet & whereColumns )
|
||||
{
|
||||
Assert( whereColumns.GetRecordInfo() == record.GetPSchema()->GetRecordInfo() );
|
||||
if ( whereColumns.GetRecordInfo() != record.GetPSchema()->GetRecordInfo() )
|
||||
return false;
|
||||
|
||||
ClearParams();
|
||||
AddRecordParameters( record, whereColumns );
|
||||
|
||||
TSQLCmdStr sStatement;
|
||||
BuildDeleteStatementText( &sStatement, record.GetPRecordInfo() );
|
||||
sStatement.Append( " WHERE " );
|
||||
AppendWhereClauseText( &sStatement, whereColumns );
|
||||
|
||||
uint32 unRowsAffected;
|
||||
if( !BYieldingExecute( sStatement, sStatement, &unRowsAffected ) )
|
||||
return false;
|
||||
|
||||
return unRowsAffected > 0 || BInTransaction();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
// CSQLUpdateOrInsert
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
CSQLUpdateOrInsert::CSQLUpdateOrInsert( const char* pszName, int nTable, const CColumnSet & whereColumns, const CColumnSet & updateColumns, const char* pszWhereClause, const char* pszUpdateClause )
|
||||
{
|
||||
const CRecordInfo* pRecordInfo = GSchemaFull().GetSchema( nTable ).GetRecordInfo();
|
||||
|
||||
//how many columns do we have
|
||||
const int nNumColumns = pRecordInfo->GetNumColumns();
|
||||
|
||||
TSQLCmdStr sStatement;
|
||||
sStatement = "MERGE INTO ";
|
||||
sStatement.Append( GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ) );
|
||||
sStatement.Append( '.' );
|
||||
sStatement.Append( pRecordInfo->GetName() );
|
||||
sStatement.Append( " WITH(HOLDLOCK) AS D USING(VALUES(" );
|
||||
sStatement.AppendFormat( "%.*s", GetInsertArgStringChars( nNumColumns ), GetInsertArgString() );
|
||||
sStatement.Append( "))AS S(" );
|
||||
|
||||
//add each column that we are adding the values for, along with the parameter from the structure
|
||||
for( int nCurrColumn = 0; nCurrColumn < nNumColumns; nCurrColumn++ )
|
||||
{
|
||||
const CColumnInfo& colInfo = pRecordInfo->GetColumnInfo( nCurrColumn );
|
||||
if( nCurrColumn != 0 )
|
||||
sStatement.Append( ',' );
|
||||
sStatement.Append( colInfo.GetName() );
|
||||
}
|
||||
|
||||
//our where clause
|
||||
sStatement.Append( ")ON " );
|
||||
|
||||
if( pszWhereClause )
|
||||
{
|
||||
sStatement.Append( pszWhereClause );
|
||||
}
|
||||
else
|
||||
{
|
||||
FOR_EACH_COLUMN_IN_SET( whereColumns, nCurrColumn )
|
||||
{
|
||||
const char* pszColName = pRecordInfo->GetColumnInfo( whereColumns.GetColumn( nCurrColumn ) ).GetName();
|
||||
if( nCurrColumn > 0 )
|
||||
sStatement.Append( " AND " );
|
||||
sStatement.AppendFormat( "D.%s=S.%s", pszColName, pszColName );
|
||||
}
|
||||
}
|
||||
|
||||
//our update clause (if they have provided fields that they want to update)
|
||||
if( pszUpdateClause || !updateColumns.IsEmpty() )
|
||||
{
|
||||
sStatement.Append( " WHEN MATCHED THEN UPDATE SET " );
|
||||
if( pszUpdateClause )
|
||||
{
|
||||
sStatement.Append( pszUpdateClause );
|
||||
}
|
||||
else
|
||||
{
|
||||
FOR_EACH_COLUMN_IN_SET( updateColumns, nCurrColumn )
|
||||
{
|
||||
const char* pszColName = pRecordInfo->GetColumnInfo( updateColumns.GetColumn( nCurrColumn ) ).GetName();
|
||||
if( nCurrColumn > 0 )
|
||||
sStatement.Append( ',' );
|
||||
sStatement.AppendFormat( "%s=S.%s", pszColName, pszColName );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//our insert clause
|
||||
sStatement.Append( " WHEN NOT MATCHED THEN INSERT(" );
|
||||
bool bFirstColumn = true;
|
||||
for( int nCurrColumn = 0; nCurrColumn < nNumColumns; nCurrColumn++ )
|
||||
{
|
||||
const CColumnInfo& colInfo = pRecordInfo->GetColumnInfo( nCurrColumn );
|
||||
if( !colInfo.BIsInsertable() )
|
||||
continue;
|
||||
|
||||
if( !bFirstColumn )
|
||||
sStatement.Append( ',' );
|
||||
bFirstColumn = false;
|
||||
sStatement.Append( colInfo.GetName() );
|
||||
}
|
||||
|
||||
sStatement.Append( ")VALUES(" );
|
||||
bFirstColumn = true;
|
||||
for( int nCurrColumn = 0; nCurrColumn < nNumColumns; nCurrColumn++ )
|
||||
{
|
||||
const CColumnInfo& colInfo = pRecordInfo->GetColumnInfo( nCurrColumn );
|
||||
if( !colInfo.BIsInsertable() )
|
||||
continue;
|
||||
|
||||
if( !bFirstColumn )
|
||||
sStatement.Append( ',' );
|
||||
bFirstColumn = false;
|
||||
sStatement.AppendFormat( "S.%s", colInfo.GetName() );
|
||||
}
|
||||
sStatement.Append( ");" );
|
||||
|
||||
//save our results so we can execute it in the future
|
||||
m_nTable = nTable;
|
||||
m_sName = pszName;
|
||||
m_sQuery = sStatement;
|
||||
}
|
||||
|
||||
bool CSQLUpdateOrInsert::BYieldingExecute( CSQLAccess& sqlAccess, const CRecordBase& record, uint32 *out_punRowsAffected /* = NULL */ ) const
|
||||
{
|
||||
AssertMsg2( record.GetITable() == m_nTable, "Error: Merge was compiled for table %s, but was attempted to be executed against %s", GSchemaFull().GetSchema( m_nTable ).GetRecordInfo()->GetName(), record.GetPRecordInfo()->GetName() );
|
||||
|
||||
const CRecordInfo* pRecordInfo = record.GetPRecordInfo();
|
||||
//how many columns do we have
|
||||
const int nNumColumns = pRecordInfo->GetNumColumns();
|
||||
|
||||
sqlAccess.ClearParams();
|
||||
for( int nCurrColumn = 0; nCurrColumn < nNumColumns; nCurrColumn++ )
|
||||
{
|
||||
const CColumnInfo& colInfo = pRecordInfo->GetColumnInfo( nCurrColumn );
|
||||
uint8 *pubData;
|
||||
uint32 cubData;
|
||||
DbgVerify( record.BGetField( nCurrColumn, &pubData, &cubData ) );
|
||||
sqlAccess.AddBindParamRaw( colInfo.GetType(), pubData, cubData );
|
||||
}
|
||||
|
||||
return sqlAccess.BYieldingExecute( m_sName, m_sQuery, out_punRowsAffected );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Adds bind parameters to the list based on a set of fields in a record
|
||||
// Input: record - record to insert
|
||||
// columnSet - The set of columns to add as params
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSQLAccess::AddRecordParameters( const CRecordBase &record, const CColumnSet & columnSet )
|
||||
{
|
||||
Assert( record.GetPSchema()->GetRecordInfo() == columnSet.GetRecordInfo() );
|
||||
if ( record.GetPSchema()->GetRecordInfo() != columnSet.GetRecordInfo() )
|
||||
return;
|
||||
|
||||
FOR_EACH_COLUMN_IN_SET( columnSet, nColumnIndex )
|
||||
{
|
||||
const CColumnInfo &columnInfo = columnSet.GetColumnInfo( nColumnIndex );
|
||||
uint8 *pubData;
|
||||
uint32 cubData;
|
||||
DbgVerify( record.BGetField( columnSet.GetColumn( nColumnIndex ), &pubData, &cubData ) );
|
||||
EGCSQLType eType = columnInfo.GetType();
|
||||
CurrentQuery()->AddBindParamRaw( eType, pubData, cubData );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Deletes all records from a table
|
||||
// Input: iTable - table to wipe
|
||||
// Output: true if the operation was successful
|
||||
// Note: PERFORMANCE WARNING: this is slow on big tables, not intended for use
|
||||
// in production
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLAccess::BYieldingWipeTable( int iTable )
|
||||
{
|
||||
// make a wipe operation
|
||||
CRecordInfo *pRecordInfo = GSchemaFull().GetSchema( iTable ).GetRecordInfo();
|
||||
|
||||
CUtlString buf;
|
||||
buf.Format( "DELETE FROM %s", pRecordInfo->GetName() );
|
||||
return BYieldingExecute( buf.String(), buf.String() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns the current query to add stuff to, creating it if there isn't
|
||||
// already a current query
|
||||
//-----------------------------------------------------------------------------
|
||||
CGCSQLQuery *CSQLAccess::CurrentQuery()
|
||||
{
|
||||
if( m_pCurrentQuery )
|
||||
return m_pCurrentQuery;
|
||||
|
||||
m_pCurrentQuery = new CGCSQLQuery();
|
||||
return m_pCurrentQuery;
|
||||
}
|
||||
|
||||
|
||||
} // namespace GCSDK
|
||||
@@ -0,0 +1,538 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
//#include "sqlaccess.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CSQLRecord::CSQLRecord( uint32 unRow, IGCSQLResultSet *pResultSet )
|
||||
{
|
||||
Init( unRow, pResultSet );
|
||||
}
|
||||
CSQLRecord::CSQLRecord()
|
||||
: m_pResultSet( NULL ), m_unRow( 0 )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CSQLRecord::~CSQLRecord()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Initializes a blank record
|
||||
// Input: iTable - table that this record will belong to
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSQLRecord::Init( uint32 unRow, IGCSQLResultSet *pResultSet )
|
||||
{
|
||||
if( unRow >= pResultSet->GetRowCount() )
|
||||
{
|
||||
m_pResultSet = NULL;
|
||||
m_unRow = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pResultSet = pResultSet;
|
||||
m_unRow = unRow;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets data for a field in this record
|
||||
// Input: unColumn - field to get
|
||||
// pubField - pointer to get filled in with pointer to data
|
||||
// cubField - pointer to get filled in with size of data
|
||||
// Output: true if successful, false if data not present
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::BGetColumnData( uint32 unColumn, uint8 **ppubField, int *pcubField )
|
||||
{
|
||||
size_t sz;
|
||||
bool bRet = BGetColumnData( unColumn, ppubField, &sz );
|
||||
*pcubField = static_cast< int >( sz );
|
||||
return bRet;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets data for a field in this record
|
||||
// Input: unColumn - field to get
|
||||
// pubField - pointer to get filled in with pointer to data
|
||||
// cubField - pointer to get filled in with size of data
|
||||
// Output: true if successful, false if data not present
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::BGetColumnData( uint32 unColumn, uint8 **ppubField, size_t *pcubField )
|
||||
{
|
||||
Assert( ppubField );
|
||||
Assert( pcubField );
|
||||
*ppubField = NULL;
|
||||
*pcubField = 0;
|
||||
|
||||
Assert( m_pResultSet );
|
||||
|
||||
if ( !BValidateColumnIndex( unColumn ) )
|
||||
return false;
|
||||
|
||||
*pcubField = 0;
|
||||
return m_pResultSet->GetData( m_unRow, unColumn, ppubField, (uint32*)pcubField );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets string data for a field in this record
|
||||
// Input: unColumn - field to get
|
||||
// ppchVal - pointer to pointer to fill in to string data
|
||||
// Output: true if successful, false if data not present or not of correct type
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::BGetStringValue( uint32 unColumn, const char **ppchVal )
|
||||
{
|
||||
Assert( ppchVal );
|
||||
*ppchVal = NULL;
|
||||
|
||||
uint8 *pubData = NULL;
|
||||
int cubData = 0;
|
||||
Assert( k_EGCSQLType_String == m_pResultSet->GetColumnType( unColumn ) );
|
||||
bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
|
||||
if ( bRet )
|
||||
*ppchVal = (const char *) pubData;
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets string data for a field in this record
|
||||
// Input: unColumn - field to get
|
||||
// ppchVal - pointer to pointer to fill in to string data
|
||||
// Output: true if successful, false if data not present or not of correct type
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::BGetStringValue( uint32 unColumn, CFmtStr1024 *psVal )
|
||||
{
|
||||
Assert( psVal );
|
||||
*psVal = "";
|
||||
|
||||
uint8 *pubData = NULL;
|
||||
int cubData = 0;
|
||||
Assert( k_EGCSQLType_String == m_pResultSet->GetColumnType( unColumn ) );
|
||||
bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
|
||||
if ( bRet )
|
||||
*psVal = (const char *) pubData;
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets int data for a field in this record
|
||||
// Input: unColumn - field to get
|
||||
// pnVal - pointer to fill in with data
|
||||
// Output: true if successful, false if data not present or not of correct type
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::BGetIntValue( uint32 unColumn, int *pnVal )
|
||||
{
|
||||
Assert( pnVal );
|
||||
*pnVal = 0;
|
||||
|
||||
uint8 *pubData = NULL;
|
||||
int cubData = 0;
|
||||
bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
|
||||
if ( bRet )
|
||||
{
|
||||
switch( m_pResultSet->GetColumnType( unColumn ) )
|
||||
{
|
||||
case k_EGCSQLType_int64:
|
||||
{
|
||||
int64 ul = *((int64 *)pubData);
|
||||
if ( ul >= LONG_MIN && ul <= LONG_MAX )
|
||||
{
|
||||
*pnVal = (int)ul;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertMsg1(false, "GetIntValue tried to catch %lld in an int, which is too small", ul );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case k_EGCSQLType_int32:
|
||||
*pnVal = *((int32 *)pubData);
|
||||
return true;
|
||||
|
||||
case k_EGCSQLType_int16:
|
||||
*pnVal = *((int16 *)pubData);
|
||||
return true;
|
||||
|
||||
case k_EGCSQLType_int8:
|
||||
*pnVal = *((int8 *)pubData);
|
||||
return true;
|
||||
|
||||
default:
|
||||
AssertMsg1(false, "GetIntValue tried to catch a %s, which is the wrong type", PchNameFromEGCSQLType( m_pResultSet->GetColumnType( unColumn ) ) );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets float data for a field in this record
|
||||
// Input: unColumn - field to get
|
||||
// pnVal - pointer to fill in with data
|
||||
// Output: true if successful, false if data not present or not of correct type
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::BGetFloatValue( uint32 unColumn, float *pfVal )
|
||||
{
|
||||
Assert( pfVal );
|
||||
*pfVal = 0.0f;
|
||||
|
||||
uint8 *pubData = NULL;
|
||||
int cubData = 0;
|
||||
bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
|
||||
if ( bRet )
|
||||
{
|
||||
Assert( k_EGCSQLType_float == m_pResultSet->GetColumnType( unColumn ) );
|
||||
AssertMsg2( sizeof( float ) == cubData, "GetValue expected %llu bytes, found %d", (uint64)sizeof( float ), cubData );
|
||||
if ( sizeof( float ) != cubData )
|
||||
return false;
|
||||
*pfVal = *( (float *) pubData );
|
||||
}
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets double data for a field in this record
|
||||
// Input: unColumn - field to get
|
||||
// pnVal - pointer to fill in with data
|
||||
// Output: true if successful, false if data not present or not of correct type
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::BGetDoubleValue( uint32 unColumn, double *pdVal )
|
||||
{
|
||||
Assert( pdVal );
|
||||
*pdVal = 0.0f;
|
||||
|
||||
uint8 *pubData = NULL;
|
||||
int cubData = 0;
|
||||
bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
|
||||
if ( bRet )
|
||||
{
|
||||
Assert( k_EGCSQLType_double == m_pResultSet->GetColumnType( unColumn ) );
|
||||
AssertMsg2( sizeof( double ) == cubData, "GetValue expected %llu bytes, found %d", (uint64)sizeof( double ), cubData );
|
||||
if ( sizeof( double ) != cubData )
|
||||
return false;
|
||||
*pdVal = *( (double *) pubData );
|
||||
}
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets int data for a field in this record
|
||||
// Input: unColumn - field to get
|
||||
// pVal - pointer to fill in with data
|
||||
// Output: true if successful, false if data not present or not of correct type
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::BGetByteValue( uint32 unColumn, byte *pVal )
|
||||
{
|
||||
Assert( pVal );
|
||||
*pVal = 0;
|
||||
|
||||
uint8 *pubData = NULL;
|
||||
int cubData = 0;
|
||||
bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
|
||||
if ( bRet )
|
||||
{
|
||||
Assert( k_EGCSQLType_int8 == m_pResultSet->GetColumnType( unColumn ) );
|
||||
AssertMsg1( 1 == cubData, "GetValue expected 1 bytes, found %d", cubData );
|
||||
if ( 1 != cubData )
|
||||
return false;
|
||||
*pVal = *( (byte *) pubData );
|
||||
}
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets int data for a field in this record
|
||||
// Input: unColumn - field to get
|
||||
// pVal - pointer to fill in with data
|
||||
// Output: true if successful, false if data not present or not of correct type
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::BGetBoolValue( uint32 unColumn, bool *pVal )
|
||||
{
|
||||
int32 b;
|
||||
if ( !BGetIntValue( unColumn, &b ) )
|
||||
return false;
|
||||
|
||||
// convert to boolean
|
||||
*pVal = ( b != 0 );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets int16 data for a field in this record
|
||||
// Input: unColumn - field to get
|
||||
// pnVal - pointer to fill in with data
|
||||
// Output: true if successful, false if data not present or not of correct type
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::BGetInt16Value( uint32 unColumn, int16 *pnVal )
|
||||
{
|
||||
Assert( pnVal );
|
||||
*pnVal = 0;
|
||||
|
||||
uint8 *pubData = NULL;
|
||||
int cubData = 0;
|
||||
bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
|
||||
if ( bRet )
|
||||
{
|
||||
Assert( k_EGCSQLType_int16 == m_pResultSet->GetColumnType( unColumn ) );
|
||||
AssertMsg1( 2 == cubData, "GetValue expected 2 bytes, found %d", cubData );
|
||||
if ( 2 != cubData )
|
||||
return false;
|
||||
*pnVal = *( (int16 *) pubData );
|
||||
}
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets int64 data for a field in this record
|
||||
// Input: unColumn - field to get
|
||||
// puVal - pointer to fill in with data
|
||||
// Output: true if successful, false if data not present or not of correct type
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::BGetInt64Value( uint32 unColumn, int64 *puVal )
|
||||
{
|
||||
Assert( puVal );
|
||||
*puVal = 0;
|
||||
|
||||
uint8 *pubData = NULL;
|
||||
int cubData = 0;
|
||||
bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
|
||||
if ( bRet )
|
||||
{
|
||||
Assert( k_EGCSQLType_int64 == m_pResultSet->GetColumnType( unColumn ) );
|
||||
AssertMsg1( 8 == cubData, "GetValue expected 8 bytes, found %d", cubData );
|
||||
if ( 8 != cubData )
|
||||
return false;
|
||||
*puVal = *( (int64 *) pubData );
|
||||
}
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets uint64 data for a field in this record
|
||||
// Input: unColumn - field to get
|
||||
// puVal - pointer to fill in with data
|
||||
// Output: true if successful, false if data not present or not of correct type
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::BGetUint64Value( uint32 unColumn, uint64 *puVal )
|
||||
{
|
||||
Assert( puVal );
|
||||
*puVal = 0;
|
||||
|
||||
uint8 *pubData = NULL;
|
||||
int cubData = 0;
|
||||
bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
|
||||
if ( bRet )
|
||||
{
|
||||
Assert( k_EGCSQLType_int64 == m_pResultSet->GetColumnType( unColumn ) );
|
||||
AssertMsg1( 8 == cubData, "GetValue expected 8 bytes, found %d", cubData );
|
||||
if ( 8 != cubData )
|
||||
return false;
|
||||
*puVal = *( (uint64 *) pubData );
|
||||
}
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets uint32 data for a field in this record
|
||||
// Input: unColumn - field to get
|
||||
// puVal - pointer to fill in with data
|
||||
// Output: true if successful, false if data not present or not of correct type
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::BGetUint32Value( uint32 unColumn, uint32 *puVal )
|
||||
{
|
||||
Assert( puVal );
|
||||
*puVal = 0;
|
||||
|
||||
uint8 *pubData = NULL;
|
||||
int cubData = 0;
|
||||
bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
|
||||
if ( bRet )
|
||||
{
|
||||
Assert( k_EGCSQLType_int32 == m_pResultSet->GetColumnType( unColumn ) );
|
||||
AssertMsg1( 4 == cubData, "GetValue expected 4 bytes, found %d", cubData );
|
||||
if ( 4 != cubData )
|
||||
return false;
|
||||
*puVal = *( (uint32 *) pubData );
|
||||
}
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets uint16 data for a field in this record
|
||||
// Input: unColumn - field to get
|
||||
// puVal - pointer to fill in with data
|
||||
// Output: true if successful, false if data not present or not of correct type
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::BGetUint16Value( uint32 unColumn, uint16 *puVal )
|
||||
{
|
||||
Assert( puVal );
|
||||
*puVal = 0;
|
||||
|
||||
uint8 *pubData = NULL;
|
||||
int cubData = 0;
|
||||
bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
|
||||
if ( bRet )
|
||||
{
|
||||
Assert( k_EGCSQLType_int16 == m_pResultSet->GetColumnType( unColumn ) );
|
||||
AssertMsg1( 2 == cubData, "GetValue expected 2 bytes, found %d", cubData );
|
||||
if ( 2 != cubData )
|
||||
return false;
|
||||
*puVal = *( (uint16 *) pubData );
|
||||
}
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Gets uint8 data for a field in this record
|
||||
// Input: unColumn - field to get
|
||||
// puVal - pointer to fill in with data
|
||||
// Output: true if successful, false if data not present or not of correct type
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::BGetUint8Value( uint32 unColumn, uint8 *puVal )
|
||||
{
|
||||
Assert( puVal );
|
||||
*puVal = 0;
|
||||
|
||||
uint8 *pubData = NULL;
|
||||
int cubData = 0;
|
||||
bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
|
||||
if ( bRet )
|
||||
{
|
||||
Assert( k_EGCSQLType_int8 == m_pResultSet->GetColumnType( unColumn ) );
|
||||
AssertMsg1( 1 == cubData, "GetValue expected 1 byte, found %d", cubData );
|
||||
if ( 1 != cubData )
|
||||
return false;
|
||||
*puVal = *( (uint8 *) pubData );
|
||||
}
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Validates column index
|
||||
// Input: unColumn - field to validate
|
||||
// Output: true if valid, false otherwise
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::BValidateColumnIndex( uint32 unColumn )
|
||||
{
|
||||
if ( unColumn >= m_pResultSet->GetColumnCount() )
|
||||
{
|
||||
AssertMsg2( false, "CSQLRecord::BValidateColumnIndex: invalid column index %d. # columns: %d", unColumn,
|
||||
m_pResultSet->GetColumnCount() );
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Advances the CSQLRecord to the next row
|
||||
// Output: returns false if this call would advance the record past the last row.
|
||||
// And makes the record invalid.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::NextRow()
|
||||
{
|
||||
Assert( m_pResultSet );
|
||||
m_unRow++;
|
||||
|
||||
if( m_unRow >= m_pResultSet->GetRowCount() )
|
||||
m_pResultSet = NULL;
|
||||
return IsValid();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Render a field to a buffer
|
||||
// Input: unColumn - field to render
|
||||
// cchBuffer - size of render buffer
|
||||
// pchBuffer - buffer to render into
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSQLRecord::RenderField( uint32 unColumn, int cchBuffer, char *pchBuffer )
|
||||
{
|
||||
Q_strncpy( pchBuffer, "", cchBuffer );
|
||||
|
||||
uint8 *pubData;
|
||||
int cubData;
|
||||
if ( !BGetColumnData( unColumn, &pubData, &cubData ) )
|
||||
return;
|
||||
|
||||
// Get the column info and figure out how to interpret the data
|
||||
ConvertFieldToText( m_pResultSet->GetColumnType( unColumn ), pubData, cubData, pchBuffer, cchBuffer, false );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Copies a CSQLRecord to CRecordBase
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSQLRecord::BWriteToRecord( CRecordBase *pRecord, const CColumnSet & csWriteFields )
|
||||
{
|
||||
bool bSuccess = true;
|
||||
FOR_EACH_COLUMN_IN_SET( csWriteFields, unSQLColumn )
|
||||
{
|
||||
uint32 unRecordColumn = csWriteFields.GetColumn( unSQLColumn );
|
||||
|
||||
uint8 *pubData;
|
||||
size_t cubData;
|
||||
if( !BGetColumnData( unSQLColumn, &pubData, &cubData ) )
|
||||
{
|
||||
bSuccess = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
bSuccess = pRecord->BSetField( unRecordColumn, pubData, cubData ) && bSuccess;
|
||||
}
|
||||
|
||||
}
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace GCSDK
|
||||
@@ -0,0 +1,918 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK
|
||||
{
|
||||
const char *GetInsertArgString()
|
||||
{
|
||||
static char s_str[1024];
|
||||
static bool s_bInit = false;
|
||||
|
||||
if ( !s_bInit )
|
||||
{
|
||||
for ( int i = 0; i < 1023; i++ )
|
||||
{
|
||||
s_str[i] = i % 2 == 0 ? '?' : ',';
|
||||
}
|
||||
|
||||
s_str[1023] = NULL;
|
||||
s_bInit = true;
|
||||
}
|
||||
|
||||
return s_str;
|
||||
}
|
||||
|
||||
uint32 GetInsertArgStringChars( uint32 nNumParams )
|
||||
{
|
||||
AssertMsg( nNumParams <= GetInsertArgStringMaxParams(), "Error: Requested more characters than are provided by the GetInsertArgString" );
|
||||
if( nNumParams == 0 )
|
||||
return 0;
|
||||
|
||||
return nNumParams * 2 - 1;
|
||||
}
|
||||
|
||||
uint32 GetInsertArgStringMaxParams()
|
||||
{
|
||||
return 512;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Converts array of field data to text for SQL IN clause
|
||||
// Input: columnInfo - schema of column being converted
|
||||
// pubData - pointer to array of data to convert
|
||||
// cubData - size of array of data
|
||||
// rgchResult - pointer to output buffer
|
||||
// cubResultLen - size of output buffer
|
||||
// bForPreparedStatement - Should we prepare the text for a prepared statement or directly place the values?
|
||||
//-----------------------------------------------------------------------------
|
||||
void ConvertFieldArrayToInText( const CColumnInfo &columnInfo, byte *pubData, int cubData, char *rgchResult, int cubResultLen, bool bForPreparedStatement )
|
||||
{
|
||||
int32 cubLength = columnInfo.GetFixedSize();
|
||||
Assert( cubData % cubLength == 0 );
|
||||
int32 nArrayCount = cubData / cubLength;
|
||||
|
||||
int32 len = 0;
|
||||
rgchResult[len++] = '(';
|
||||
for( int i = 0; i < nArrayCount; ++i )
|
||||
{
|
||||
if ( bForPreparedStatement )
|
||||
{
|
||||
if ( i < nArrayCount - 1 )
|
||||
{
|
||||
rgchResult[len++] = '?';
|
||||
rgchResult[len++] = ',';
|
||||
}
|
||||
else
|
||||
{
|
||||
rgchResult[len++] = '?';
|
||||
rgchResult[len++] = ')';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch ( columnInfo.GetType() )
|
||||
{
|
||||
case k_EGCSQLType_int8:
|
||||
if ( i < nArrayCount - 1 )
|
||||
len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d,", *( (byte *) pubData ) );
|
||||
else
|
||||
len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d)", *( (byte *) pubData ) );
|
||||
break;
|
||||
case k_EGCSQLType_int16:
|
||||
if ( i < nArrayCount - 1 )
|
||||
len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d,", *( (short *) pubData ) );
|
||||
else
|
||||
len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d)", *( (short *) pubData ) );
|
||||
break;
|
||||
case k_EGCSQLType_int32:
|
||||
if ( i < nArrayCount - 1 )
|
||||
len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d,", *( (int *) pubData ) );
|
||||
else
|
||||
len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d)", *( (int *) pubData ) );
|
||||
break;
|
||||
case k_EGCSQLType_int64:
|
||||
if ( i < nArrayCount - 1 )
|
||||
len += Q_snprintf( rgchResult + len, cubResultLen - len, "%lld,", *( (int64 *) pubData ) );
|
||||
else
|
||||
len += Q_snprintf( rgchResult + len, cubResultLen - len, "%lld)", *( (int64 *) pubData ) );
|
||||
break;
|
||||
default:
|
||||
AssertMsg( false, "Unsupported data type for non prepares statement with IN clause\n" );
|
||||
rgchResult[0] = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if( len >= cubResultLen - 1 )
|
||||
{
|
||||
AssertMsg( false, "Generation of IN clause foverflowed\n" );
|
||||
rgchResult[0] = 0;
|
||||
return;
|
||||
}
|
||||
pubData += cubLength;
|
||||
}
|
||||
|
||||
rgchResult[len] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Converts field data to text equivalent for SQL statement
|
||||
// Input: eFieldType - The type of the field to convert to text
|
||||
// pubRecord - pointer to record data to convert
|
||||
// cubRecord - size of record data
|
||||
// rgchField - pointer to output buffer
|
||||
// cchField - size of output buffer
|
||||
//-----------------------------------------------------------------------------
|
||||
void ConvertFieldToText( EGCSQLType eFieldType, uint8 *pubRecord, int cubRecord, char *rgchField, int cchField, bool bQuoteString )
|
||||
{
|
||||
char rgchTmp[k_cMedBuff];
|
||||
|
||||
switch ( eFieldType )
|
||||
{
|
||||
case k_EGCSQLType_int8:
|
||||
Q_snprintf( rgchField, cchField, "%d", *( (byte *) pubRecord ) );
|
||||
break;
|
||||
case k_EGCSQLType_int16:
|
||||
Q_snprintf( rgchField, cchField, "%d", *( (short *) pubRecord ) );
|
||||
break;
|
||||
case k_EGCSQLType_int32:
|
||||
Q_snprintf( rgchField, cchField, "%d", *( (int *) pubRecord ) );
|
||||
break;
|
||||
case k_EGCSQLType_int64:
|
||||
Q_snprintf( rgchField, cchField, "%lld", *( (int64 *) pubRecord ) );
|
||||
break;
|
||||
case k_EGCSQLType_float:
|
||||
Q_snprintf( rgchField, cchField, "%f", *((float*) pubRecord) );
|
||||
break;
|
||||
case k_EGCSQLType_double:
|
||||
Q_snprintf( rgchField, cchField, "%f", *((double*) pubRecord) );
|
||||
break;
|
||||
case k_EGCSQLType_String:
|
||||
if ( pubRecord && *pubRecord )
|
||||
{
|
||||
Assert( cubRecord + 1 < Q_ARRAYSIZE( rgchTmp ) );
|
||||
|
||||
Q_memcpy( rgchTmp, (char *) pubRecord, cubRecord );
|
||||
rgchTmp[cubRecord] = 0;
|
||||
|
||||
if ( bQuoteString )
|
||||
{
|
||||
EscapeStringValue( rgchTmp, Q_ARRAYSIZE( rgchTmp ) );
|
||||
Q_snprintf( rgchField, cchField, "'%s'", rgchTmp );
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_strncpy( rgchField, rgchTmp, cchField );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( bQuoteString )
|
||||
{
|
||||
Q_strncpy( rgchField, "''", cchField );
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_strncpy( rgchField, "", cchField );
|
||||
}
|
||||
}
|
||||
break;
|
||||
case k_EGCSQLType_Blob:
|
||||
case k_EGCSQLType_Image:
|
||||
Q_strncpy( rgchField, "0x", cchField );
|
||||
Q_binarytohex( pubRecord, cubRecord, rgchField + 2, cchField - 2 );
|
||||
break;
|
||||
default:
|
||||
Assert( false );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns the text SQL type for a given field
|
||||
// Input: field - field to determine type for
|
||||
// pchBuf - pointer to output buffer
|
||||
// cchBuf - size of output buffer
|
||||
// Output: returns pchBuf for convenience of one-line usage
|
||||
//-----------------------------------------------------------------------------
|
||||
char *SQLTypeFromField( const CColumnInfo &colInfo, char *pchBuf, int cchBuf )
|
||||
{
|
||||
EGCSQLType eType = colInfo.GetType();
|
||||
*pchBuf = 0;
|
||||
switch ( eType )
|
||||
{
|
||||
case k_EGCSQLType_int8:
|
||||
Q_strncpy( pchBuf, "TINYINT", cchBuf );
|
||||
break;
|
||||
case k_EGCSQLType_int16:
|
||||
Q_strncpy( pchBuf, "SMALLINT", cchBuf );
|
||||
break;
|
||||
case k_EGCSQLType_int32:
|
||||
Q_strncpy( pchBuf, "INT", cchBuf );
|
||||
break;
|
||||
case k_EGCSQLType_int64:
|
||||
Q_strncpy( pchBuf, "BIGINT", cchBuf );
|
||||
break;
|
||||
case k_EGCSQLType_float:
|
||||
Q_strncpy( pchBuf, "REAL", cchBuf );
|
||||
break;
|
||||
case k_EGCSQLType_double:
|
||||
Q_strncpy( pchBuf, "FLOAT", cchBuf );
|
||||
break;
|
||||
case k_EGCSQLType_String:
|
||||
Q_snprintf( pchBuf, cchBuf, "VARCHAR(%d)", colInfo.GetMaxSize() );
|
||||
break;
|
||||
case k_EGCSQLType_Blob:
|
||||
Q_snprintf( pchBuf, cchBuf, "VARBINARY(%d)", colInfo.GetMaxSize() );
|
||||
break;
|
||||
case k_EGCSQLType_Image:
|
||||
Q_strncpy( pchBuf, "IMAGE", cchBuf );
|
||||
break;
|
||||
default:
|
||||
Assert( false );
|
||||
break;
|
||||
}
|
||||
|
||||
return pchBuf;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Escapes any single quotes to a string value to double single quotes
|
||||
// Input: rgchField - text to escape
|
||||
// cchField - size of text buffer
|
||||
// Notes: The text will be escaped and expanded in place in the buffer.
|
||||
// In the worst case, the text may expand by 2x. (If the field is all
|
||||
// single quotes.) So, you must pass in a buffer which is at least
|
||||
// twice as long as the text length so we can guarantee to be able to
|
||||
// escape the string.
|
||||
//-----------------------------------------------------------------------------
|
||||
void EscapeStringValue( char *rgchField, int cchField )
|
||||
{
|
||||
// TODO - what else do we need to escape? %() ...
|
||||
char *pubCur = rgchField;
|
||||
int nLen = 0;
|
||||
int cSingleQuotes = 0;
|
||||
|
||||
// This function gets called on every text field we write but most text fields
|
||||
// don't need to be escaped, so try to be as fast as possible in the normal case.
|
||||
|
||||
// first, walk through the string and count the string length and number of single quotes
|
||||
while ( *pubCur )
|
||||
{
|
||||
if ( '\'' == *pubCur )
|
||||
cSingleQuotes++;
|
||||
nLen ++;
|
||||
pubCur++;
|
||||
}
|
||||
|
||||
// if no single quotes, nothing to do
|
||||
if ( !cSingleQuotes )
|
||||
return;
|
||||
|
||||
// caller must pass in a buffer that's long enough for expansion
|
||||
Assert( nLen + cSingleQuotes + 1 <= cchField );
|
||||
if ( !( nLen + cSingleQuotes + 1 <= cchField ) )
|
||||
return;
|
||||
|
||||
// We know exactly how many characters the string will expand by (the # of single quotes). Walk backward
|
||||
// and copy the characters into the right places. This touches each character only once.
|
||||
pubCur = rgchField + nLen + cSingleQuotes;
|
||||
*pubCur = 0;
|
||||
pubCur--;
|
||||
while ( pubCur > rgchField && cSingleQuotes > 0 )
|
||||
{
|
||||
// read pointer is offset from write pointer by # of remaining single quotes
|
||||
char *pubRead = pubCur - cSingleQuotes;
|
||||
Assert( pubRead >= rgchField );
|
||||
// copy each character
|
||||
*pubCur = *pubRead;
|
||||
if ( '\'' == *pubRead )
|
||||
{
|
||||
// if the character is a single quote, back up one more and insert another single quote to escape it
|
||||
pubCur --;
|
||||
*pubCur = '\'';
|
||||
// decrement # of single quotes remaining
|
||||
cSingleQuotes --;
|
||||
Assert( cSingleQuotes >= 0 );
|
||||
}
|
||||
pubCur--;
|
||||
}
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Adds constraint information to a SQL command to add or remove constraint
|
||||
// Input: pchTableName - name of table
|
||||
// pchColumnName - name of column
|
||||
// nColFlagConstraint - flag with which constraint to
|
||||
// bForAdd - whether constraint is being added or removed
|
||||
// pchCmd - buffer to append SQL command to
|
||||
// cchCmd - size of buffer
|
||||
//-----------------------------------------------------------------------------
|
||||
void AppendConstraint( const char *pchTableName, const char *pchColumnName, int nColFlagConstraint, bool bForAdd,
|
||||
bool bClustered, CFmtStrMax & sCmd, int nFillFactor )
|
||||
{
|
||||
Assert( pchTableName && pchTableName[0] );
|
||||
Assert( pchColumnName && pchColumnName[0] );
|
||||
|
||||
switch ( nColFlagConstraint )
|
||||
{
|
||||
case k_nColFlagPrimaryKey:
|
||||
sCmd.AppendFormat( " CONSTRAINT %s_%s_PrimaryKey", pchTableName, pchColumnName);
|
||||
if ( bForAdd )
|
||||
{
|
||||
sCmd += " PRIMARY KEY ";
|
||||
if ( bClustered )
|
||||
{
|
||||
sCmd.AppendFormat( " CLUSTERED WITH (FILLFACTOR = %d) ", nFillFactor );
|
||||
}
|
||||
else
|
||||
{
|
||||
sCmd += "NONCLUSTERED";
|
||||
}
|
||||
}
|
||||
break;
|
||||
case k_nColFlagUnique:
|
||||
/* do nothing - the uniqueness will be handled by creation of an index */
|
||||
break;
|
||||
case k_nColFlagAutoIncrement:
|
||||
sCmd += " IDENTITY";
|
||||
break;
|
||||
default:
|
||||
AssertMsg( false, "CSQLThread::AppendContraint: invalid constraint type" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Adds constraint information to a SQL command to add or remove constraint
|
||||
// Input: pRecordInfo - record info describing table
|
||||
// pColumnInfo - record info describing column
|
||||
// bForAdd - whether constraint is being added or removed
|
||||
// pchCmd - buffer to append SQL command to
|
||||
// cchCmd - size of buffer
|
||||
//-----------------------------------------------------------------------------
|
||||
void AppendConstraints( const CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfo, bool bForAdd, CFmtStrMax & sCmd )
|
||||
{
|
||||
Assert( pRecordInfo != NULL );
|
||||
Assert( pColumnInfo != NULL );
|
||||
|
||||
if ( pColumnInfo->BIsPrimaryKey() )
|
||||
{
|
||||
// any column in a PK can't be NULL.
|
||||
if ( bForAdd )
|
||||
{
|
||||
sCmd += " NOT NULL";
|
||||
}
|
||||
|
||||
// only add primary key constraint here if it is a single-column PK
|
||||
if ( pRecordInfo->GetPrimaryKeyType() == k_EPrimaryKeyTypeSingle )
|
||||
{
|
||||
// get the fields on the primary key
|
||||
const CUtlVector< FieldSet_t > &refFields = pRecordInfo->GetIndexFields( );
|
||||
int nFillFactor = refFields.Element( pRecordInfo->GetPKIndex() ).GetFillFactor();
|
||||
AppendConstraint( pRecordInfo->GetName(), pColumnInfo->GetName(), k_nColFlagPrimaryKey, bForAdd, pColumnInfo->BIsClustered(), sCmd, nFillFactor );
|
||||
}
|
||||
}
|
||||
else if ( pColumnInfo->BIsUnique() )
|
||||
{
|
||||
AppendConstraint( pRecordInfo->GetName(), pColumnInfo->GetName(), k_nColFlagUnique, bForAdd, pColumnInfo->BIsClustered(), sCmd, 0 );
|
||||
}
|
||||
|
||||
if ( pColumnInfo->BIsAutoIncrement() )
|
||||
{
|
||||
AppendConstraint( pRecordInfo->GetName(), pColumnInfo->GetName(), k_nColFlagAutoIncrement, bForAdd, pColumnInfo->BIsClustered(), sCmd, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Generates the "CONSTRAINT ..." text for the table primary key
|
||||
//-----------------------------------------------------------------------------
|
||||
void BuildTablePKConstraintText( TSQLCmdStr *psStatement, CRecordInfo *pRecordInfo )
|
||||
{
|
||||
const FieldSet_t& vecFields = pRecordInfo->GetPKFields( );
|
||||
|
||||
psStatement->sprintf( "CONSTRAINT %s_PrimaryKey PRIMARY KEY %s ( ",
|
||||
pRecordInfo->GetName(),
|
||||
vecFields.IsClustered() ? "CLUSTERED" : "NONCLUSTERED" );
|
||||
|
||||
for ( int nField = 0; nField < vecFields.GetCount(); nField++ )
|
||||
{
|
||||
// what field is the next column in our index?
|
||||
int nThisField = vecFields.GetField( nField );
|
||||
const CColumnInfo& columnInfo = pRecordInfo->GetColumnInfo(nThisField);
|
||||
|
||||
if (nField != 0)
|
||||
{
|
||||
*psStatement += ", ";
|
||||
}
|
||||
*psStatement += columnInfo.GetName();
|
||||
}
|
||||
|
||||
// close our list
|
||||
*psStatement += ") ";
|
||||
|
||||
if ( vecFields.GetFillFactor() != 0 )
|
||||
{
|
||||
// non-default fill factor, so specify it
|
||||
psStatement->AppendFormat( " WITH FILLFACTOR = %d ",
|
||||
vecFields.GetFillFactor() );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Adds constraint information to a SQL command to add or remove table-level constraints
|
||||
// Input: pRecordInfo - record info describing table
|
||||
// pchCmd - buffer to append SQL command to
|
||||
// cchCmd - size of buffer
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void AppendTableConstraints( CRecordInfo *pRecordInfo, CFmtStrMax & sCmd )
|
||||
{
|
||||
// the only supported table constraint is for PKs or FKs
|
||||
if ( pRecordInfo->GetPrimaryKeyType() == k_EPrimaryKeyTypeMulti )
|
||||
{
|
||||
TSQLCmdStr tmp;
|
||||
BuildTablePKConstraintText( &tmp, pRecordInfo );
|
||||
sCmd += ", ";
|
||||
sCmd += tmp;
|
||||
}
|
||||
|
||||
// Look for FKs required on this table
|
||||
// the only supported table constraint is for PKs or FKs
|
||||
int cFKs = pRecordInfo->GetFKCount();
|
||||
for( int i=0; i < cFKs; ++i )
|
||||
{
|
||||
FKData_t &fkData = pRecordInfo->GetFKData( i );
|
||||
|
||||
CFmtStr sColumns, sParentColumns;
|
||||
FOR_EACH_VEC( fkData.m_VecColumnRelations, nCol )
|
||||
{
|
||||
FKColumnRelation_t &colRelation = fkData.m_VecColumnRelations[nCol];
|
||||
if ( nCol > 0)
|
||||
{
|
||||
sColumns += ",";
|
||||
sParentColumns += ",";
|
||||
}
|
||||
sColumns += colRelation.m_rgchCol;
|
||||
sParentColumns += colRelation.m_rgchParentCol;
|
||||
}
|
||||
|
||||
TSQLCmdStr sTmp;
|
||||
sTmp.sprintf( ", CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s(%s) ON DELETE %s ON UPDATE %s",
|
||||
fkData.m_rgchName, sColumns.Access(), fkData.m_rgchParentTableName, sParentColumns.Access(),
|
||||
PchNameFromEForeignKeyAction( fkData.m_eOnDeleteAction ), PchNameFromEForeignKeyAction( fkData.m_eOnUpdateAction ) );
|
||||
|
||||
// add to the command
|
||||
sCmd += sTmp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Builds a SQL INSERT statement
|
||||
// Input: psStatement - The string to put the statement into
|
||||
// pRecordInfo - record info describing table inserting into
|
||||
//-----------------------------------------------------------------------------
|
||||
void BuildInsertStatementText( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo )
|
||||
{
|
||||
psStatement->sprintf("INSERT INTO %s.%s (", GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName() );
|
||||
|
||||
// build a string of the field names
|
||||
int cColumns = pRecordInfo->GetNumColumns();
|
||||
int nInsertable = 0;
|
||||
bool bAddedBefore = false;
|
||||
for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
|
||||
{
|
||||
const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
|
||||
if ( !columnInfo.BIsInsertable() )
|
||||
continue;
|
||||
|
||||
nInsertable++;
|
||||
|
||||
if ( bAddedBefore )
|
||||
psStatement->Append( ',' );
|
||||
bAddedBefore = true;
|
||||
psStatement->Append( columnInfo.GetName() );
|
||||
}
|
||||
|
||||
psStatement->AppendFormat( ") VALUES (%.*s)", GetInsertArgStringChars( nInsertable ), GetInsertArgString() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Builds a SQL INSERT statement
|
||||
// IMPORTANT NOTE - This Insert statement will use the Microsoft SQL Server
|
||||
// specific clause 'OUTPUT Inserted.ColumnName'
|
||||
// The result of that will be that the SQL statement will return to us
|
||||
// the columns that could not be specified by the Insert.
|
||||
// At the time of writing, that is primarily AutoIncrement columns,
|
||||
// however in theory we should be able to recover any computed column
|
||||
// from SQL server, with the caveats specified at :
|
||||
// http://msdn.microsoft.com/en-us/library/ms177564.aspx
|
||||
//
|
||||
// Input: psStatement - The output statement string
|
||||
// pRecordInfo - record info describing table inserting into
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void BuildInsertAndReadStatementText( TSQLCmdStr *psStatement, CUtlVector<int> *pvecOutputFields, const CRecordInfo *pRecordInfo )
|
||||
{
|
||||
psStatement->sprintf("INSERT INTO %s.%s (", GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName() );
|
||||
|
||||
// build a string of the field names
|
||||
int nInsertable = 0;
|
||||
int cColumns = pRecordInfo->GetNumColumns();
|
||||
bool bAddedBefore = false;
|
||||
for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
|
||||
{
|
||||
const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
|
||||
if ( !columnInfo.BIsInsertable() )
|
||||
continue;
|
||||
|
||||
nInsertable++;
|
||||
|
||||
if ( bAddedBefore )
|
||||
psStatement->Append( ',' );
|
||||
bAddedBefore = true;
|
||||
psStatement->Append( columnInfo.GetName() );
|
||||
}
|
||||
|
||||
bAddedBefore = false ;
|
||||
int nOutputColumn = 0;
|
||||
for( int iColumn = 0; iColumn < cColumns; iColumn++ )
|
||||
{
|
||||
const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn ) ;
|
||||
|
||||
//
|
||||
// If we can't Insert it - we want SQL Server to tell us what value was stored
|
||||
// in the column !!
|
||||
//
|
||||
if( !columnInfo.BIsInsertable() )
|
||||
{
|
||||
if( bAddedBefore )
|
||||
psStatement->Append( ", INSERTED." );
|
||||
else
|
||||
psStatement->Append( ") OUTPUT INSERTED." );
|
||||
bAddedBefore = true ;
|
||||
psStatement->Append( columnInfo.GetName() );
|
||||
pvecOutputFields->AddToTail( iColumn );
|
||||
nOutputColumn++;
|
||||
}
|
||||
}
|
||||
|
||||
// add field values to SQL statement
|
||||
psStatement->AppendFormat( " VALUES (%.*s)", GetInsertArgStringChars( nInsertable ), GetInsertArgString() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Builds a SQL MERGE statement update or insert using in-flight values table
|
||||
// Input: psStatement - The string to put the statement into
|
||||
// pRecordInfo - record info describing table inserting into
|
||||
//-----------------------------------------------------------------------------
|
||||
void BuildMergeStatementTextOnPKWhenMatchedUpdateWhenNotMatchedInsert( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo )
|
||||
{
|
||||
psStatement->sprintf( "MERGE INTO %s.%s WITH( HOLDLOCK, ROWLOCK ) T USING ( VALUES (%.*s) ) AS S(",
|
||||
GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName(),
|
||||
GetInsertArgStringChars( pRecordInfo->GetNumColumns() ), GetInsertArgString() );
|
||||
|
||||
{
|
||||
int cColumns = pRecordInfo->GetNumColumns();
|
||||
for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
|
||||
{
|
||||
const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
|
||||
if ( iColumn )
|
||||
psStatement->Append( ',' );
|
||||
psStatement->Append( columnInfo.GetName() );
|
||||
}
|
||||
}
|
||||
|
||||
psStatement->Append( ") ON " );
|
||||
|
||||
// build a string of the PK columns
|
||||
const FieldSet_t &fsPK = pRecordInfo->GetIndexFields()[pRecordInfo->GetPKIndex()];
|
||||
{
|
||||
int cColumns = fsPK.GetCount();
|
||||
for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
|
||||
{
|
||||
const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( fsPK.GetField( iColumn ) );
|
||||
if ( iColumn )
|
||||
psStatement->Append( " AND " );
|
||||
psStatement->Append( "T." );
|
||||
psStatement->Append( columnInfo.GetName() );
|
||||
psStatement->Append( "=S." );
|
||||
psStatement->Append( columnInfo.GetName() );
|
||||
}
|
||||
}
|
||||
|
||||
psStatement->Append( " WHEN MATCHED THEN UPDATE SET " );
|
||||
|
||||
// build the update string
|
||||
{
|
||||
int cColumns = pRecordInfo->GetNumColumns();
|
||||
bool bAddedBefore = false;
|
||||
for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
|
||||
{
|
||||
bool bThisColumnIsPartOfPK = false;
|
||||
for ( int ipkCheck = 0; ipkCheck < fsPK.GetCount(); ++ipkCheck )
|
||||
{
|
||||
if ( iColumn == fsPK.GetField( ipkCheck ) )
|
||||
{
|
||||
bThisColumnIsPartOfPK = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( bThisColumnIsPartOfPK )
|
||||
continue;
|
||||
|
||||
const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
|
||||
if ( bAddedBefore )
|
||||
psStatement->Append( ',' );
|
||||
bAddedBefore = true;
|
||||
psStatement->Append( columnInfo.GetName() );
|
||||
psStatement->Append( "=S." );
|
||||
psStatement->Append( columnInfo.GetName() );
|
||||
}
|
||||
}
|
||||
|
||||
psStatement->Append( " WHEN NOT MATCHED BY TARGET THEN INSERT (" );
|
||||
|
||||
// build a string of the field names
|
||||
{
|
||||
int cColumns = pRecordInfo->GetNumColumns();
|
||||
bool bAddedBefore = false;
|
||||
for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
|
||||
{
|
||||
const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
|
||||
if ( !columnInfo.BIsInsertable() )
|
||||
continue;
|
||||
|
||||
if ( bAddedBefore )
|
||||
psStatement->Append( ',' );
|
||||
bAddedBefore = true;
|
||||
psStatement->Append( columnInfo.GetName() );
|
||||
}
|
||||
}
|
||||
|
||||
psStatement->Append( ") VALUES (" );
|
||||
{
|
||||
int cColumns = pRecordInfo->GetNumColumns();
|
||||
bool bAddedBefore = false;
|
||||
for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
|
||||
{
|
||||
const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
|
||||
if ( !columnInfo.BIsInsertable() )
|
||||
continue;
|
||||
|
||||
if ( bAddedBefore )
|
||||
psStatement->Append( ',' );
|
||||
bAddedBefore = true;
|
||||
psStatement->Append( "S." );
|
||||
psStatement->Append( columnInfo.GetName() );
|
||||
}
|
||||
}
|
||||
psStatement->Append( ");" );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Builds a SQL MERGE statement using CTE_MergeParams as supplied table holding rows
|
||||
// Input: psStatement - The string to put the statement into
|
||||
// pRecordInfo - record info describing table inserting into
|
||||
//-----------------------------------------------------------------------------
|
||||
void BuildMergeStatementTextOnPKWhenNotMatchedInsert( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo )
|
||||
{
|
||||
psStatement->sprintf( "MERGE INTO %s.%s WITH( HOLDLOCK, ROWLOCK ) T USING ( VALUES (%.*s) ) AS S(",
|
||||
GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName(),
|
||||
GetInsertArgStringChars( pRecordInfo->GetNumColumns() ), GetInsertArgString() );
|
||||
|
||||
{
|
||||
int cColumns = pRecordInfo->GetNumColumns();
|
||||
for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
|
||||
{
|
||||
const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
|
||||
if ( iColumn )
|
||||
psStatement->Append( ',' );
|
||||
psStatement->Append( columnInfo.GetName() );
|
||||
}
|
||||
}
|
||||
|
||||
psStatement->Append( ") ON " );
|
||||
|
||||
// build a string of the PK columns
|
||||
const FieldSet_t &fsPK = pRecordInfo->GetIndexFields()[pRecordInfo->GetPKIndex()];
|
||||
{
|
||||
int cColumns = fsPK.GetCount();
|
||||
for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
|
||||
{
|
||||
const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( fsPK.GetField( iColumn ) );
|
||||
if ( iColumn )
|
||||
psStatement->Append( " AND " );
|
||||
psStatement->Append( "T." );
|
||||
psStatement->Append( columnInfo.GetName() );
|
||||
psStatement->Append( "=S." );
|
||||
psStatement->Append( columnInfo.GetName() );
|
||||
}
|
||||
}
|
||||
|
||||
psStatement->Append( " WHEN NOT MATCHED BY TARGET THEN INSERT (" );
|
||||
|
||||
// build a string of the field names
|
||||
{
|
||||
int cColumns = pRecordInfo->GetNumColumns();
|
||||
bool bAddedBefore = false;
|
||||
for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
|
||||
{
|
||||
const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
|
||||
if ( !columnInfo.BIsInsertable() )
|
||||
continue;
|
||||
|
||||
if ( bAddedBefore )
|
||||
psStatement->Append( ',' );
|
||||
bAddedBefore = true;
|
||||
psStatement->Append( columnInfo.GetName() );
|
||||
}
|
||||
}
|
||||
|
||||
psStatement->Append( ") VALUES (" );
|
||||
{
|
||||
int cColumns = pRecordInfo->GetNumColumns();
|
||||
bool bAddedBefore = false;
|
||||
for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
|
||||
{
|
||||
const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
|
||||
if ( !columnInfo.BIsInsertable() )
|
||||
continue;
|
||||
|
||||
if ( bAddedBefore )
|
||||
psStatement->Append( ',' );
|
||||
bAddedBefore = true;
|
||||
psStatement->Append( "S." );
|
||||
psStatement->Append( columnInfo.GetName() );
|
||||
}
|
||||
}
|
||||
psStatement->Append( ");" );
|
||||
}
|
||||
|
||||
void BuildSelectStatementText( TSQLCmdStr *psStatement, const CColumnSet & selectSet, const char *pchTopClause )
|
||||
{
|
||||
*psStatement = "SELECT ";
|
||||
|
||||
if( pchTopClause )
|
||||
{
|
||||
psStatement->Append( pchTopClause );
|
||||
psStatement->Append( ' ' );
|
||||
}
|
||||
|
||||
// build a string of the field names
|
||||
bool bAddedBefore = false;
|
||||
FOR_EACH_COLUMN_IN_SET( selectSet, nColumnIndex )
|
||||
{
|
||||
const CColumnInfo &columnInfo = selectSet.GetColumnInfo( nColumnIndex );
|
||||
if ( bAddedBefore )
|
||||
psStatement->Append( ',' );
|
||||
bAddedBefore = true;
|
||||
psStatement->Append( columnInfo.GetName() );
|
||||
}
|
||||
|
||||
psStatement->Append( " FROM ");
|
||||
psStatement->Append( GSchemaFull().GetDefaultSchemaNameForCatalog( selectSet.GetRecordInfo()->GetESchemaCatalog() ) );
|
||||
psStatement->Append( '.' );
|
||||
psStatement->Append( selectSet.GetRecordInfo()->GetName() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Builds a SQL UPDATE statement
|
||||
// Input: pRecordInfo - record info describing table inserting into
|
||||
// bForPreparedStatement - if true, inserts values as '?' for later
|
||||
// binding. If false, values are inserted in text.
|
||||
// pchStatement - pointer to buffer to build statement in
|
||||
// cchStatement - size of buffer
|
||||
// pSQLRecord - pointer to record with data to update
|
||||
// iColumnMatch - column to use for WHERE condition
|
||||
// pvMatch - data value to use for WHERE condition
|
||||
// cubMatch - size of pvMatch data
|
||||
// rgiColumnUpdate - array of column #'s to update
|
||||
// ciColumnUpdate - count of column #'s to update
|
||||
//-----------------------------------------------------------------------------
|
||||
void BuildUpdateStatementText( TSQLCmdStr *psStatement, const CColumnSet & updateColumns )
|
||||
{
|
||||
// build the UPDATE statement
|
||||
psStatement->sprintf( "UPDATE %s.%s SET ", GSchemaFull().GetDefaultSchemaNameForCatalog( updateColumns.GetRecordInfo()->GetESchemaCatalog() ), updateColumns.GetRecordInfo()->GetName() );
|
||||
|
||||
// add each field we're updating to the UPDATE statement
|
||||
FOR_EACH_COLUMN_IN_SET( updateColumns, nColumnIndex )
|
||||
{
|
||||
const CColumnInfo &columnInfo = updateColumns.GetColumnInfo( nColumnIndex );
|
||||
|
||||
if( nColumnIndex > 0 )
|
||||
psStatement->Append( ',' );
|
||||
psStatement->Append( columnInfo.GetName() );
|
||||
psStatement->Append( "=?" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Builds a SQL UPDATE statement
|
||||
//-----------------------------------------------------------------------------
|
||||
void BuildDeleteStatementText( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo )
|
||||
{
|
||||
psStatement->sprintf( "DELETE FROM %s.%s", GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName() );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Builds a where clause for the provided fields
|
||||
//-----------------------------------------------------------------------------
|
||||
void AppendWhereClauseText( TSQLCmdStr *psClause, const CColumnSet & columnSet )
|
||||
{
|
||||
// add each field we're updating to the UPDATE statement
|
||||
FOR_EACH_COLUMN_IN_SET( columnSet, nColumnIndex )
|
||||
{
|
||||
const CColumnInfo &columnInfo = columnSet.GetColumnInfo( nColumnIndex );
|
||||
|
||||
if( nColumnIndex > 0 )
|
||||
psClause->Append( " AND ");
|
||||
psClause->Append( columnInfo.GetName() );
|
||||
psClause->Append( "=?" );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Builds an OUTPUT [fields] INTO [table] for the provided fields/data
|
||||
//-----------------------------------------------------------------------------
|
||||
void BuildOutputClauseText( TSQLCmdStr *psClause, const CColumnSet & columnSet )
|
||||
{
|
||||
*psClause = " OUTPUT ";
|
||||
|
||||
FOR_EACH_COLUMN_IN_SET( columnSet, nColumnIndex )
|
||||
{
|
||||
const CColumnInfo &columnInfo = columnSet.GetColumnInfo( nColumnIndex );
|
||||
|
||||
if( nColumnIndex > 0 )
|
||||
psClause->Append( ", ");
|
||||
|
||||
psClause->Append( " ? AS " );
|
||||
psClause->Append( columnInfo.GetName() );
|
||||
}
|
||||
|
||||
psClause->Append( " INTO " );
|
||||
psClause->Append( columnSet.GetRecordInfo()->GetName() );
|
||||
}
|
||||
|
||||
////-----------------------------------------------------------------------------
|
||||
//// Purpose: our own special "upsert" into a column with a uniqueness constraint
|
||||
////-----------------------------------------------------------------------------
|
||||
//EResult UpdateOrInsertUnique( CSQLAccess &sqlAccess, int iTable, int iField, CRecordBase *pRecordBase, int iIndexID )
|
||||
//{
|
||||
// // attempt an update - if it fails due to duplicate primary key, they can't use this
|
||||
// // url (it's taken) - if it succeeds but affects 0 rows, they didn't have a vanity url
|
||||
// // and we need to do an insert (which could again fail due to primary key constraints)
|
||||
// int cRecordsUpdated = 0;
|
||||
// bool bRet = sqlAccess.BYieldingUpdateFieldFromRecordWithIndex( iTable, &cRecordsUpdated, iField, pRecordBase, iIndexID );
|
||||
// if ( !bRet )
|
||||
// {
|
||||
// // ODBC is the suck - give me Spring JDBC templates, please.
|
||||
// if ( sqlAccess.GetLastError()->IsDuplicateInsertAttempt() )
|
||||
// {
|
||||
// return k_EResultDuplicateName;
|
||||
// }
|
||||
// return k_EResultFail;
|
||||
// }
|
||||
// else if ( 0 == cRecordsUpdated )
|
||||
// {
|
||||
// // the user didn't have an entry, so insert one.
|
||||
// bRet = sqlAccess.BYieldingInsertRecord( iTable, pRecordBase );
|
||||
// if ( !bRet )
|
||||
// {
|
||||
// // ODBC is the suck - give me Spring JDBC templates, please.
|
||||
// if ( sqlAccess.GetLastError()->IsDuplicateInsertAttempt() )
|
||||
// {
|
||||
// return k_EResultDuplicateName;
|
||||
// }
|
||||
// return k_EResultFail;
|
||||
// }
|
||||
// }
|
||||
// return k_EResultOK;
|
||||
//}
|
||||
//
|
||||
|
||||
} // namespace GCSDK
|
||||
@@ -0,0 +1,107 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//
|
||||
// A string class that keeps all its memory on the stack and performs no
|
||||
// memory allocation
|
||||
//=============================================================================//
|
||||
|
||||
#ifndef STACKSTRING_H
|
||||
#define STACKSTRING_H
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
|
||||
template< size_t cubSize >
|
||||
class CStackString
|
||||
{
|
||||
public:
|
||||
class CStackString() { Reset(); }
|
||||
class CStackString( const char *pchRHS ) { Set( pchRHS ); }
|
||||
|
||||
void Reset() { m_rchBuffer[0] = 0; m_pchEnd = m_rchBuffer; }
|
||||
void Set( const char *pchValue );
|
||||
const char *Get() const { return m_rchBuffer; }
|
||||
static size_t GetCubSize() { return cubSize; }
|
||||
size_t Length() const { return m_pchEnd - &m_rchBuffer[0]; }
|
||||
|
||||
CStackString & operator=( const char *pchValue ) { Set( pchValue ); return *this; }
|
||||
CStackString & operator+=( const char *pchValue ) { Append( pchValue ); return *this; }
|
||||
operator const char*() const { return m_rchBuffer; }
|
||||
|
||||
void Format( PRINTF_FORMAT_STRING const char *pchFormat, ... );
|
||||
void AppendFormat( PRINTF_FORMAT_STRING const char *pchFormat, ... );
|
||||
void AppendFormatV( const char *pchFormat, va_list marker );
|
||||
void Append( const char *pchValue );
|
||||
|
||||
void SetIndent( uint32 unCount, char chIndent = '\t' );
|
||||
private:
|
||||
char m_rchBuffer[ cubSize ];
|
||||
char *m_pchEnd;
|
||||
};
|
||||
|
||||
typedef CStackString<256> CStackString256;
|
||||
typedef CStackString<512> CStackString512;
|
||||
typedef CStackString<1024> CStackString1024;
|
||||
typedef CStackString<8192> CStackStringMax;
|
||||
|
||||
template< size_t cubSize >
|
||||
void CStackString<cubSize>::Set( const char *pchValue )
|
||||
{
|
||||
V_strcpy_safe( m_rchBuffer, pchValue );
|
||||
m_pchEnd = &m_rchBuffer[Q_strlen(m_rchBuffer)];
|
||||
}
|
||||
|
||||
template< size_t cubSize >
|
||||
void CStackString<cubSize>::AppendFormatV( const char *pchFormat, va_list marker )
|
||||
{
|
||||
size_t cubPrinted = Q_vsnprintf( m_pchEnd, GetCubSize() - Length(), pchFormat, marker );
|
||||
m_pchEnd += cubPrinted;
|
||||
}
|
||||
|
||||
|
||||
template< size_t cubSize >
|
||||
void CStackString<cubSize>::Format( PRINTF_FORMAT_STRING const char *pchFormat, ... )
|
||||
{
|
||||
va_list marker;
|
||||
va_start( marker, pchFormat );
|
||||
size_t cubPrinted = Q_vsnprintf( m_rchBuffer, GetCubSize(), pchFormat, marker );
|
||||
m_pchEnd = m_rchBuffer + cubPrinted;
|
||||
va_end( marker );
|
||||
}
|
||||
|
||||
|
||||
template< size_t cubSize >
|
||||
void CStackString<cubSize>::AppendFormat( PRINTF_FORMAT_STRING const char *pchFormat, ... )
|
||||
{
|
||||
va_list marker;
|
||||
va_start( marker, pchFormat );
|
||||
AppendFormatV( pchFormat, marker );
|
||||
va_end( marker );
|
||||
}
|
||||
|
||||
template< size_t cubSize >
|
||||
void CStackString<cubSize>::Append( const char *pchValue )
|
||||
{
|
||||
Q_strcat( m_rchBuffer, pchValue, GetCubSize() );
|
||||
m_pchEnd = m_rchBuffer + Q_strlen(m_rchBuffer);
|
||||
}
|
||||
|
||||
template< size_t cubSize >
|
||||
void CStackString<cubSize>::SetIndent( uint32 unCount, char chIndent )
|
||||
{
|
||||
Reset();
|
||||
for ( uint32 x = 0; x < unCount && x < (GetCubSize()-1); x++ )
|
||||
{
|
||||
*m_pchEnd = chIndent;
|
||||
m_pchEnd++;
|
||||
}
|
||||
*m_pchEnd = '\0';
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,9 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
// stdafx.cpp : source file that includes just the standard includes
|
||||
// stdafx.pch will be the pre-compiled header
|
||||
// stdafx.obj will contain the pre-compiled type information
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
// TODO: reference any additional headers you need in STDAFX.H
|
||||
// and not in this file
|
||||
@@ -0,0 +1,14 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
// stdafx.h : include file for standard system include files,
|
||||
// or project specific include files that are used frequently, but
|
||||
// are changed infrequently
|
||||
//
|
||||
|
||||
#include "gcsdk/gcsdk_auto.h"
|
||||
|
||||
#include "tier1/kvpacker.h"
|
||||
|
||||
using GCSDK::SPEW_SYSTEM_MISC;
|
||||
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
@@ -0,0 +1,611 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
#ifndef CLIENTENUMS_H
|
||||
#define CLIENTENUMS_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
|
||||
enum ELogonState
|
||||
{
|
||||
k_ELogonStateNotLoggedOn = 0,
|
||||
k_ELogonStateLoggingOn = 1,
|
||||
k_ELogonStateLoggingOff = 2,
|
||||
k_ELogonStateLoggedOn = 3
|
||||
};
|
||||
|
||||
|
||||
// Enums for all personal questions supported by the system.
|
||||
enum EPersonalQuestion
|
||||
{
|
||||
// Never ever change these after initial release.
|
||||
k_EPSMsgNameOfSchool = 0, // Question: What is the name of your school?
|
||||
k_EPSMsgFavoriteTeam = 1, // Question: What is your favorite team?
|
||||
k_EPSMsgMothersName = 2, // Question: What is your mother's maiden name?
|
||||
k_EPSMsgNameOfPet = 3, // Question: What is the name of your pet?
|
||||
k_EPSMsgChildhoodHero = 4, // Question: Who was your childhood hero?
|
||||
k_EPSMsgCityBornIn = 5, // Question: What city were you born in?
|
||||
|
||||
k_EPSMaxPersonalQuestion
|
||||
};
|
||||
|
||||
|
||||
// account flags (stored in DB)
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASEd
|
||||
enum EAccountFlags
|
||||
{
|
||||
m_EAccountFlagNormalUser = 0, // Standard user level (yes, this is meant to be zero)
|
||||
k_EAccountFlagPersonaNameSet = ( 1 << 0 ), // true if the user has set the persona name they really want, instead of the auto-generated one
|
||||
k_EAccountFlagUnbannable = ( 1 << 1 ), // whatever happens, this account can't be banned
|
||||
k_EAccountFlagPasswordSet = ( 1 << 2 ), // we've set the password at least once on this account
|
||||
k_EAccountFlagSupport = ( 1 << 3 ), // Enables use of web support tool
|
||||
k_EAccountFlagAdmin = ( 1 << 4 ), // The name says it all, can do everything
|
||||
k_EAccountFlagSupervisor = ( 1 << 5 ), // support supervisory role
|
||||
k_EAccountFlagAppEditor = ( 1 << 6 ), // Can edit app info
|
||||
k_EAccountFlagHWIDSet = ( 1 << 7 ), // Set HWID once
|
||||
k_EAccountFlagPersonalQASet = ( 1 << 8 ), // user has personal Question & anser set
|
||||
k_EAccountFlagVacBeta = ( 1 << 9 ), // user participates in VAC beta tests
|
||||
k_EAccountFlagDebug = ( 1 << 10 ), // user is in debug mode, eg VAC doesn't kick etc
|
||||
k_EAccountFlagDisabled = ( 1 << 11 ), // account is disabled.
|
||||
k_EAccountFlagLimitedUser = ( 1 << 12 ), // account is limited user account because it doesnt own anything
|
||||
k_EAccountFlagLimitedUserForce = ( 1 << 13 ), // account is limited user account because we forced it to be
|
||||
k_EAccountFlagEmailValidated = ( 1 << 14 ), // user has verified email address via WG
|
||||
k_EAccountFlagMarketingTreatment = ( 1 << 15), // account is flagged as being in a treatment for marketing/sales experiments
|
||||
};
|
||||
|
||||
// profile state (stored in DB)
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
enum ECommunityProfileState
|
||||
{
|
||||
k_ECommunityProfileNotCreated = 0, // user hasn't setup community account yet
|
||||
k_ECommunityProfileActive = 1, // user joined community, site is public
|
||||
k_ECommunityProfilePrivate = 2, // user joined community, site is private
|
||||
k_ECommunityProfileLocked = 3, // user got locked, content can't be changed but is still accessible
|
||||
k_ECommunityProfileDisabled = 4, // user got disabled, site not accessible anymore
|
||||
};
|
||||
|
||||
|
||||
// profile privacy option setting (stored in DB)
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
enum ECommunityPrivacyState
|
||||
{
|
||||
k_ECommunityPrivacyInvalid = 0,
|
||||
k_ECommunityPrivacyPrivate = 1, // ain't nobody can see it
|
||||
k_ECommunityPrivacyFriendsOnly = 2, // only your friends can see it
|
||||
k_ECommunityPrivacyPublic = 3, // anybody could see it
|
||||
};
|
||||
|
||||
enum ECommunityVisibilityState
|
||||
{
|
||||
k_ECommunityVisibilityPrivate = 1, // private, requester see only public fields
|
||||
k_ECommunityVisibilityFriendsOnly = 2, // friends only, requester sees only public fields
|
||||
k_ECommunityVisibilityOpen = 3, // it is visible to requester; they are owner or friend or public
|
||||
k_ECommunityVisibilitySupportPrivate = 4, // was private, but it's a support account asking
|
||||
k_ECommunityVisibilitySupportFriendsOnly = 5,// was friends only, but it's a support account asking
|
||||
};
|
||||
|
||||
enum ECommentPermission
|
||||
{
|
||||
k_ECommentPermissionFriendsOnly = 0, // only friends can leave a comment
|
||||
k_ECommentPermissionAnyone = 1, // anybody can leave a comment
|
||||
k_ECommentPermissionSelfOnly = 2, // only the account owner can leave a comment
|
||||
};
|
||||
|
||||
// Payment methods for purchases - BIT FLAGS so can be used to indicate
|
||||
// acceptable payment methods for packages
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
enum EPaymentMethod
|
||||
{
|
||||
k_EPaymentMethodNone = 0x000, // user got the license for free
|
||||
k_EPaymentMethodActivationCode = 0x001, // user paid by entering unused CD-Key or other activation code
|
||||
k_EPaymentMethodCreditCard = 0x002, // user paid with credit card
|
||||
k_EPaymentMethodPayPal = 0x004, // user paid with via paypal
|
||||
k_EPaymentMethodGuestPass = 0x008, // user paid by redeeming a guest pass
|
||||
k_EPaymentMethodHardwarePromo = 0x010, // user presented machine credentials
|
||||
k_EPaymentMethodClickAndBuy = 0x020, // ClickandBuy
|
||||
k_EPaymentMethodAutoGrant = 0x040, // server side purchased package, things like German specific TF2 free weekend
|
||||
k_EPaymentMethodWallet = 0x080, // user paid with wallet
|
||||
k_EPaymentMethodOEMTicket = 0x100, // user paid by redeeming a OEM license ticket
|
||||
k_EPaymentMethodSplit = 0x200, // user paid with wallet AND a provider
|
||||
};
|
||||
|
||||
// Sources for WalletTxn records
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
enum EWalletSource
|
||||
{
|
||||
k_EWalletSourceInvalid = 0,
|
||||
k_EWalletSourcePurchase = 1, // Created from a purchase, refund, chargeback, or reverse chargeback (PurchaseRefGID -> TransID)
|
||||
k_EWalletSourceGuestPass = 2, // Created from a guest pass (PurchaseRefGID -> GuestPassID)
|
||||
k_EWalletSourceConversion = 3, // Created from a wallet conversion (PurchaseRefGID -> GID shared between debit & credit records)
|
||||
k_EWalletSourceRebate = 4, // Created from a rebate (PurchaseRefGID -> TransID)
|
||||
};
|
||||
|
||||
// License types
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
enum ELicenseType
|
||||
{
|
||||
k_ENoLicense = 0, // for shipped goods
|
||||
k_ESinglePurchase = 1, // single purchase
|
||||
k_ESinglePurchaseLimitedUse = 2, // single purchase w/ expiration
|
||||
k_ERecurringCharge = 3, // recurring subscription
|
||||
k_ERecurringChargeLimitedUse = 4, // recurring subscription w/ limited minutes per period
|
||||
k_ERecurringChargeLimitedUseWithOverages = 5, // like above but w/ soft limit and overage charges
|
||||
};
|
||||
|
||||
// Flags for licenses - BITS
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
enum ELicenseFlags
|
||||
{
|
||||
k_ELicenseFlagNone = 0x00, // just a place holder
|
||||
k_ELicenseFlagRenew = 0x01, // Renew this license next period
|
||||
k_ELicenseFlagRenewalFailed = 0x02, // Auto-renew failed
|
||||
k_ELicenseFlagPending = 0x04, // Purchase or renewal is pending
|
||||
k_ELicenseFlagExpired = 0x08, // Set if no longer active (whatever the reason)
|
||||
k_ELicenseFlagCancelledByUser = 0x10, // Cancelled by the user
|
||||
k_ELicenseFlagCancelledByAdmin = 0x20, // Cancelled by customer support
|
||||
k_ELicenseFlagLowViolenceContent = 0x40,// license is for low violence content
|
||||
};
|
||||
|
||||
// Status of a package
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
enum EPackageStatus
|
||||
{
|
||||
k_EPackageAvailable = 0, // Available for purchase and use
|
||||
k_EPackagePreorder = 1, // Available for purchase, as a pre-order
|
||||
k_EPackageUnavailable = 2, // Not available for new purchases, may still be owned
|
||||
k_EPackageInvalid = 3, // Either an unknown package or a deleted one that nobody should own
|
||||
};
|
||||
|
||||
// Purchase status
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
enum EPurchaseStatus
|
||||
{
|
||||
k_EPurchasePending = 0, // purchase is pending, valid but pending subscription
|
||||
k_EPurchaseSucceeded = 1, // purchase successful, valid subscription
|
||||
k_EPurchaseFailed = 2, // purchase failed, no subscription
|
||||
k_EPurchaseRefunded = 3, // we refunded the purchase and removed subscription
|
||||
k_EPurchaseInit = 4, // user started purchase
|
||||
k_EPurchaseChargedback = 5, // the user issued a chargeback, we removed subscription
|
||||
k_EPurchaseRevoked = 6, // we revoked the purchase and removed subscription. Usually stolen CD-Keys
|
||||
k_EPurchaseInDispute = 7, // the purchase is being disputed by the user, preliminary to a chargeback
|
||||
};
|
||||
|
||||
// LineItemTypes
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASEd
|
||||
enum ELineItemType
|
||||
{
|
||||
k_ELineItemTypeInvalid = 0, // Unknown - load all purchase line items
|
||||
k_ELineItemTypeMicroTxn = ( 1 << 0 ), // Transaction has data in MicroTxnLineItem table
|
||||
k_ELineItemTypeWallet = ( 1 << 1 ), // Transaction has data in WalletLineItem table
|
||||
k_ELineItemTypePkg = ( 1 << 2 ), // Transaction has data in PurchaseLineItem table
|
||||
};
|
||||
|
||||
// Enum for the types of news push items you can get
|
||||
enum ENewsUpdateType
|
||||
{
|
||||
k_EAppNews = 0, // news about a particular app
|
||||
k_ESteamAds = 1, // Marketing messages
|
||||
k_ESteamNews = 2, // EJ's corner and the like
|
||||
k_ECDDBUpdate = 3, // backend has a new CDDB for you to load
|
||||
k_EClientUpdate = 4, // new version of the steam client is available
|
||||
};
|
||||
|
||||
// Detailed purchase result codes for the client
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
enum EPurchaseResultDetail
|
||||
{
|
||||
k_EPurchaseResultNoDetail = 0,
|
||||
k_EPurchaseResultAVSFailure = 1,
|
||||
k_EPurchaseResultInsufficientFunds = 2,
|
||||
k_EPurchaseResultContactSupport = 3,
|
||||
k_EPurchaseResultTimeout = 4,
|
||||
|
||||
k_EPurchaseResultInvalidPackage = 5,
|
||||
k_EPurchaseResultInvalidPaymentMethod = 6,
|
||||
k_EPurchaseResultInvalidData = 7,
|
||||
k_EPurchaseResultOthersInProgress = 8,
|
||||
k_EPurchaseResultAlreadyPurchased = 9,
|
||||
k_EPurchaseResultWrongPrice = 10,
|
||||
k_EPurchaseResultFraudCheckFailed = 11,
|
||||
k_EPurchaseResultCancelledByUser = 12,
|
||||
k_EPurchaseResultRestrictedCountry = 13,
|
||||
k_EPurchaseResultBadActivationCode = 14, // this code gives no receipt
|
||||
k_EPurchaseResultDuplicateActivationCode = 15,
|
||||
|
||||
k_EPurchaseResultUseOtherPaymentMethod = 16, // User should try a different payment method
|
||||
k_EPurchaseResultUseOtherFundingSource = 17, // Select a different funding source (paypal)
|
||||
k_EPurchaseResultInvalidShippingAddress = 18, // Shipping address is invalid (paypal)
|
||||
k_EPurchaseResultRegionNotSupported = 19, // This region is not supported with this payment type
|
||||
|
||||
k_EPurchaseResultAcctIsBlocked = 20, // Acct has been blocked by provider - user should contact provider to resolve
|
||||
k_EPurchaseResultAcctNotVerified = 21, // Provider indicated account needs to be verified for transaction to complete
|
||||
|
||||
k_EPurchaseResultInvalidAccount = 22, // Provider indicated the account is invalid or no longer usable
|
||||
k_EPurchaseResultStoreBillingCountryMismatch = 23, // store country code & billing country code do not match
|
||||
k_EPurchaseResultDoesNotOwnRequiredApp = 24, // user does not own one of the apps required for purchase
|
||||
k_EPurchaseResultCanceledByNewTransaction = 25, // user made a new transaction which canceled an old, pending transaction
|
||||
k_EPurchaseResultForceCanceledPending = 26, // A pending transaction was force canceled (no response from provider)
|
||||
k_EPurchaseResultFailCurrencyTransProvider = 27, // selected transaction provider does not support calculated currency
|
||||
k_EPurchaseResultFailedCyberCafe = 28, // cybercafe account tried to purchase or use an activation code
|
||||
|
||||
k_EPurchaseResultNeedsPreApproval = 29, // Transaction needs approval from support
|
||||
k_EPurchaseResultPreApprovalDenied = 30, // Transaction was denied by support
|
||||
k_EPurchaseResultWalletCurrencyMismatch = 31, // Currency of purchase does not match currency of wallet
|
||||
};
|
||||
|
||||
// Type of system IM. The client can use this to do special UI handling in specific circumstances
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
enum ESystemIMType
|
||||
{
|
||||
k_ESystemIMRawText = 0,
|
||||
k_ESystemIMInvalidCard = 1,
|
||||
k_ESystemIMRecurringPurchaseFailed = 2,
|
||||
k_ESystemIMCardWillExpire = 3,
|
||||
k_ESystemIMSubscriptionExpired = 4,
|
||||
k_ESystemIMGuestPassReceived = 5, // User has received a guest pass from a friend
|
||||
k_ESystemIMGuestPassGranted = 6, // System has granted a user a guest pass to give out
|
||||
k_ESystemIMGiftRevoked = 7, // We revoked a gift due to chargeback, etc
|
||||
|
||||
//
|
||||
k_ESystemIMTypeMax
|
||||
};
|
||||
|
||||
// Ways an external cd key can be munged onto a users PC
|
||||
enum ELegacyKeyRegistrationMethod
|
||||
{
|
||||
eLegacyKeyRegistrationMethodNone = 0, // doesn't support legacy cd keys
|
||||
eLegacyKeyRegistrationMethodRegistry, // just place it into the registry
|
||||
eLegacyKeyRegistrationMethodDisk, // put it in a file on disk
|
||||
};
|
||||
|
||||
// Support events, generated by system or support input
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
enum ESupportEvent
|
||||
{
|
||||
// support activity
|
||||
eSupportNote = 0, // a generic support note
|
||||
eSupportLogin = 1, // support account logged in out, data: IP:Port
|
||||
eSupportLogoff = 2, // support account logged out, text: IP:Port
|
||||
eSupportTicketCreated = 3, // a support ticket was created
|
||||
eSupportTicketClosed = 4, // problem was solved, ticket closed
|
||||
|
||||
// account changes
|
||||
eSupportEnableAccount = 5, // support enabled account, text: reason
|
||||
eSupportDisableAccount = 6, // support disabled account, text: reason
|
||||
eSupportChangeAccountPassword = 7, // support or user changed password, data: DONT include old password
|
||||
eSupportChangeAccountEmail = 8, // support or user changed email, data: old email
|
||||
eSupportChangeAccountName = 9, // support or user changed name, data: old name
|
||||
eSupportChangeAccountPlayer = 10, // support or user changed player name, data: old name
|
||||
|
||||
eSupportPurchaseChargedback = 11, // a charge back was issued
|
||||
eSupportPurchaseRefunded = 12, // a refund was issued
|
||||
eSupportPurchaseForcedCompletion = 13, // support forced a pending purchase to complete
|
||||
|
||||
// license handling
|
||||
eSupportLicenseAdded = 14, // support added a license, text: reason
|
||||
eSupportLicenseCanceled = 15, // support or user cancel a license
|
||||
eSupportLicenseChanged = 16, // support removed a license, text: reason
|
||||
eSupportBannedGame = 17, // support banned game for an account
|
||||
eSupportUnbannedGame = 18, // support unbanned game for an account
|
||||
|
||||
// purchase activity
|
||||
eSupportRunPurchase = 19,
|
||||
eSupportChangedCreditCard = 20, // support updated a credit card
|
||||
eSupportSetNoFraudCheckFlag = 21, // support disabled fraud check, data: reason
|
||||
|
||||
// banning
|
||||
eSupportBannedCreditCard = 22, // support banned a credit card
|
||||
eSupportBannedIP = 23, // support banned an IP
|
||||
eSupportBannedCDKey = 24, // support banned an CDKey
|
||||
eSupportBannedCountry = 25, // support banned a country
|
||||
eSupportBannedPayPalAccount = 26,
|
||||
|
||||
eSupportPurchaseCanceled = 27, // support forced a pending purchase to cancel
|
||||
|
||||
eSupportChangeAvatar = 28,
|
||||
eSupportChangeProfileURL = 29,
|
||||
|
||||
eSupportRegisterCDKey = 30, // support added a CD key to this account
|
||||
eSupportGrantGuestPass = 31, // support granted a guest pass to this account
|
||||
eSupportResubmitTransaction = 32, // support resubmitted a transaction
|
||||
|
||||
eSupportResetContent = 33, // reset user content based on abuse reports
|
||||
eSupportLockProfile = 34, // temp block a user from modifying community content
|
||||
eSupportSetCommunityState = 35, // perm lock a user profile, can't be modified
|
||||
eSupportDeleteAbuseReports = 36, // deleted abuse reports for the SteamID
|
||||
eSupportSetAccountFlags = 37, // changed account flags
|
||||
|
||||
eSupportChargebackStatusUpdate = 38, // support updated the pending chargeback status
|
||||
|
||||
eSupportRefundForcedCompletion = 39, // support forced a pending refund to complete
|
||||
eSupportRefundCanceled = 40, // support forced a pending refund to cancel
|
||||
|
||||
eSupportRevokeActivationKey = 41, // support revoked and unlocked activation key
|
||||
eSupportReverseChargeback = 42, // a charge back was reversed
|
||||
eSupportRejectedActivationKey = 43, // activation key was rejected (invalid or already used)
|
||||
|
||||
eSupportPurchaseError = 44, // Purchase error
|
||||
eSupportAudited = 45,
|
||||
|
||||
// items
|
||||
eSupportBanItems = 46, // support banned a user's items
|
||||
eSupportRestoreBannedItems = 47, // support restored banned items
|
||||
eSupportRestoreDeletedItems = 48, // support restored deleted items
|
||||
|
||||
// comments
|
||||
eSupportDeleteComments = 49, // cleared comments on this account (not written by this account)
|
||||
|
||||
eSupportDeleteItems = 50, // support deleted a user's items
|
||||
eSupportDeleteCachedCard = 51, // support deleted a user's cached credit card
|
||||
|
||||
eSupportConvertedWallet = 52, // user's wallet was converted
|
||||
|
||||
eSupportTxnApproved = 53, // PreApproval granted
|
||||
eSupportTxnDenied = 54, // PreApproval denied
|
||||
|
||||
eSupportGCAction = 55, // used for all support actions from the GC
|
||||
};
|
||||
|
||||
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
enum ESupportTicket
|
||||
{
|
||||
// all kinds of problems tickets that have to be handled by support
|
||||
k_ETicketUnknown = 0, // an unknown problem. yay.
|
||||
k_ETicketManual = 1, // a problem manually entered by support.
|
||||
k_ETicketFraudRedFlag = 2, // fraud detection marked this account
|
||||
k_ETicketPurchaseError = 3, // a purchase error happened
|
||||
k_ETicketChargeback = 4, // a chargeback needs attention
|
||||
};
|
||||
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
enum ESupportTicketState
|
||||
{
|
||||
// all kinds of problems tickets that have to be handled by support
|
||||
k_ETicketStateUnknown = 0, // support issue state is unknown
|
||||
k_ETicketStateUnassigned = 1, // support issue is 'open' but not assigned yet
|
||||
k_ETicketStateInProcess = 2, // support issue is assigned to an support actor
|
||||
k_ETicketStateResolved = 3, // support issue is fixed and closed
|
||||
k_ETicketStateUnresolved = 4, // support issue couldn't be fixed. closed anyway
|
||||
k_ETicketStateAutoClosed = 5, // System closed the ticket automatically
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// types of content that can be reported as abused
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
//-----------------------------------------------------------------------------
|
||||
enum ECommunityContentType
|
||||
{
|
||||
k_EContentUnspecified = 0,
|
||||
k_EContentAll = 1, // reset all community content
|
||||
k_EContentAvatarImage = 2, // clear avatar image
|
||||
k_EContentProfileText = 3, // reset profile text
|
||||
k_EContentWebLinks = 4, // delete web links
|
||||
k_EContentAnnouncement = 5,
|
||||
k_EContentEventText = 6,
|
||||
k_EContentCustomCSS = 7,
|
||||
k_EContentProfileURL = 8, // delete community URL ID
|
||||
k_EContentComments = 9, // just comments this guy has written
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// types of reasons why a violation report was issued
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
//-----------------------------------------------------------------------------
|
||||
enum EAbuseReportType
|
||||
{
|
||||
k_EAbuseUnspecified = 0,
|
||||
k_EAbuseInappropriate = 1, // just not ok to post
|
||||
k_EAbuseProhibited = 2, // prohibited by EULA or general law
|
||||
k_EAbuseSpamming = 3, // excessive spamming
|
||||
k_EAbuseAdvertisement = 4, // unwanted advertisement
|
||||
k_EAbuseExploit = 5, // content data attempts to exploit code issue
|
||||
k_EAbuseSpoofing = 6, // user/group is impersonating an official contact
|
||||
k_EAbuseLanguage = 7, // bad language
|
||||
k_EAbuseAdultContent = 8, // any kind of adult material, references etc
|
||||
k_EAbuseHarassment = 9, // harassment, discrimination, racism etc
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// actions for a user within a clan for logging in the ClanHistory table
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
enum EClanAction
|
||||
{
|
||||
k_EJoined = 1, // joined the clan
|
||||
k_ELeft = 2, // left the clan
|
||||
k_EPromoted = 3, // promoted to officer
|
||||
k_EDemoted = 4, // demoted from officer
|
||||
k_EKicked = 5, // kicked off the clan
|
||||
k_ECreated = 6, // clan was created
|
||||
k_EInvited = 7, // invited someone
|
||||
k_EEventCreated = 8, // clan event created
|
||||
k_EEventUpdated = 9, // clan event updated
|
||||
k_EEventDeleted = 10, // clan event deleted
|
||||
k_EPermissionsChanged = 11, // clan permissions were changed
|
||||
k_EAnnouncementCreated = 12, // clan announcement created
|
||||
k_EAnnouncementUpdated = 13, // clan announcement updated
|
||||
k_EAnnouncementDeleted = 14, // clan announcement deleted
|
||||
k_EPOTWChanged = 15, // changed the POTW
|
||||
k_ELinksChanged = 16, // links changed
|
||||
k_EDetailsChanged = 17, // details changed
|
||||
k_ESupportResetContent = 18, // support reset some or all of the clan content
|
||||
k_ESupportLockedGroup = 19, // support locked this clan, it can't be modified anymore
|
||||
k_ESupportUnlockedGroup = 20, // support unlocked this clan
|
||||
k_ESupportChangedOwner = 21, // support transfered ownership
|
||||
k_EMadePublic = 22, // made from private into public
|
||||
k_EMadePrivate = 23, // made from public into private
|
||||
k_ESupportDisabledGroup = 24, // support disabled this group, nobody can see it anymore
|
||||
k_EKickedChat = 25, // kicked from chat
|
||||
k_EBannedChat = 26, // banned from chat
|
||||
k_EUnBannedChat = 27, // un-banned from chat
|
||||
|
||||
k_EHighestValidAction // keep me updated, please!
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// types of events for use in the Clan Event Type table
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
//-----------------------------------------------------------------------------
|
||||
enum EClanEventType
|
||||
{
|
||||
k_EOtherEvent = 1,
|
||||
k_EGameEvent = 2,
|
||||
k_EPartyEvent = 3,
|
||||
k_EMeetingEvent = 4,
|
||||
k_ESpecialCauseEvent = 5,
|
||||
k_EMusicAndArtsEvent = 6,
|
||||
k_ESportsEvent = 7,
|
||||
k_ETripEvent = 8,
|
||||
k_EChatEvent = 9,
|
||||
k_EGameReleaseEvent = 10,
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// types of marketing messages displayed to users
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
//-----------------------------------------------------------------------------
|
||||
enum EMarketingMessageType
|
||||
{
|
||||
k_EMarketingMessageNowAvailable = 1,
|
||||
k_EMarketingMessageWeekendDeal = 2,
|
||||
k_EMarketingMessagePrePurchase = 3,
|
||||
k_EMarketingMessagePlayNow = 4,
|
||||
k_EMarketingMessagePreloadNow = 5,
|
||||
k_EMarketingMessageGeneral = 6,
|
||||
k_EMarketingMessageDemoQuit = 7,
|
||||
k_EMarketingMessageGifting = 8,
|
||||
k_EMarketingMessageEJsKorner = 9,
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// types of associations a marketing message may have
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
//-----------------------------------------------------------------------------
|
||||
enum EMarketingMessageAssociationType
|
||||
{
|
||||
k_EMarketingMessageNoAssociation = 0,
|
||||
k_EMarketingMessageAppAssociation = 1,
|
||||
k_EMarketingMessageSubscriptionAssociation = 2,
|
||||
k_EMarketingMessagePublisherAssociation = 3,
|
||||
k_EMarketingMessageGenreAssociation = 4,
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Marketing message visibility
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
//-----------------------------------------------------------------------------
|
||||
enum EMarketingMessageVisibility
|
||||
{
|
||||
k_EMarketingMessageVisibleBeta = 1,
|
||||
k_EMarketingMessageVisiblePublic = 2,
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Structures used in multiple messages
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purchase message constants
|
||||
// WARNING: Do not change these if an instance of this record may exist in a database!!!
|
||||
// BUGBUG derrick - Since these also define schema, they should be moved into steamschema.h
|
||||
const int k_cchCCNumMax = 16 + 1;
|
||||
const int k_cchHolderNameMax = 100 + 1;
|
||||
const int k_cchExpYearMax = 4 + 1;
|
||||
const int k_cchExpMonthMax = 2 + 1;
|
||||
const int k_cchCVV2Max = 4 + 1;
|
||||
const int k_cchAddressMax = 128 + 1;
|
||||
const int k_cchAddress2Max = k_cchAddressMax;
|
||||
const int k_cchCityMax = 50 + 1;
|
||||
const int k_cchPostcodeMax = 16 + 1;
|
||||
const int k_cchStateMax = 32 + 1;
|
||||
const int k_cchPhoneMax = 20 + 1;
|
||||
const int k_cchEmailMax = 100 + 1;
|
||||
const int k_cchCountryCodeMax = 2 + 1;
|
||||
const int k_cchPayPalCheckoutTokenMax = 20 + 1;
|
||||
const int k_cchStateCodeMax = 3 + 1;
|
||||
const int k_cchCurrencyCodeMax = 3 + 1;
|
||||
|
||||
const int k_cubMaxDfsURL = 128; // Max size for URL descriptors on DFS
|
||||
|
||||
|
||||
// License information
|
||||
struct LicenseInfo_t
|
||||
{
|
||||
PackageId_t m_unPackageID;
|
||||
RTime32 m_RTime32Created;
|
||||
RTime32 m_RTime32NextProcess;
|
||||
int32 m_nMinuteLimit;
|
||||
int32 m_nMinutesUsed;
|
||||
EPaymentMethod m_ePaymentMethod;
|
||||
uint32 m_nFlags;
|
||||
char m_rgchPurchaseCountryCode[k_cchCountryCodeMax];
|
||||
int32 m_nTerritoryCode;
|
||||
};
|
||||
|
||||
// Supported Currency Codes
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
enum ECurrencyCode
|
||||
{
|
||||
k_ECurrencyCodeInvalid = 0,
|
||||
k_ECurrencyCodeUSD = 1,
|
||||
k_ECurrencyCodeGBP = 2,
|
||||
k_ECurrencyCodeEUR = 3,
|
||||
|
||||
k_ECurrencyCodeMax = 4
|
||||
};
|
||||
|
||||
enum ETaxType
|
||||
{
|
||||
k_ETaxTypeInvalid = 0,
|
||||
k_ETaxTypeUSState = 1,
|
||||
k_ETaxTypeVAT = 2
|
||||
};
|
||||
|
||||
// client stat list
|
||||
// needs to be kept in the same order, since it's part of the protocol
|
||||
enum EClientStat
|
||||
{
|
||||
k_EClientStatP2PConnectionsUDP = 0,
|
||||
k_EClientStatP2PConnectionsRelay = 1,
|
||||
k_EClientStatP2PGameConnections = 2,
|
||||
k_EClientStatP2PVoiceConnections = 3,
|
||||
|
||||
k_EClientStatBytesDownloaded = 4,
|
||||
|
||||
k_EClientStatMax, // must be last, used as array's of data
|
||||
};
|
||||
|
||||
enum EP2PState
|
||||
{
|
||||
k_EP2PStateNotConnected,
|
||||
k_EP2PStateUDP,
|
||||
k_EP2PStateRelay,
|
||||
};
|
||||
|
||||
// User response for authentication request
|
||||
enum EMicroTxnAuthResponse
|
||||
{
|
||||
k_EMicroTxnAuthResponseInvalid = 0, // Invalid value
|
||||
k_EMicroTxnAuthResponseAuthorize = 1, // user accepted microtransaction
|
||||
k_EMicroTxnAuthResponseDeny = 2, // user denied microtransaction
|
||||
k_EMicroTxnAuthResponseAutoDeny = 3, // client automatically denied microtransaction (user wasn't in game, etc.)
|
||||
};
|
||||
|
||||
// Result of authorization request, returned to client
|
||||
enum EMicroTxnAuthResult
|
||||
{
|
||||
k_EMicroTxnAuthResultInvalid = 0, // Invalid value
|
||||
k_EMicroTxnAuthResultOK = 1, // Successfully authorized
|
||||
k_EMicroTxnAuthResultFail = 2, // An error occurred
|
||||
k_EMicroTxnAuthResultInsufficientFunds = 3, // User has insufficient funds to complete microtransaction
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,33 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef IGAMECOORDINATOR_H
|
||||
#define IGAMECOORDINATOR_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
typedef uint32 AppId_t;
|
||||
class CSteamID;
|
||||
class IGameCoordinatorHost;
|
||||
class IGCSQLResultSetList;
|
||||
|
||||
class IGameCoordinator
|
||||
{
|
||||
public:
|
||||
virtual bool BInit( AppId_t unAppID, const char *pchAppPath, IGameCoordinatorHost *pHost ) = 0;
|
||||
virtual bool BMainLoopOncePerFrame( uint64 ulLimitMicroseconds ) = 0;
|
||||
virtual bool BMainLoopUntilFrameCompletion( uint64 ulLimitMicroseconds ) = 0;
|
||||
virtual void Shutdown() = 0;
|
||||
virtual void Uninit() = 0;
|
||||
virtual void MessageFromClient( const CSteamID & senderID, uint32 unMsgType, void *pubData, uint32 cubData ) = 0;
|
||||
virtual void Validate( CValidator &validator, const char *pchName ) = 0;
|
||||
virtual void SQLResults( GID_t gidContextID ) = 0;
|
||||
};
|
||||
|
||||
#define GAMECOORDINATOR_INTERFACE_VERSION "GAMECOORDINATOR003"
|
||||
|
||||
#endif // IGAMECOORDINATOR_H
|
||||
@@ -0,0 +1,31 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Provides an interface that the server hosting a GC must implement
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
#ifndef IGAMECOORDINATORHOST_H
|
||||
#define IGAMECOORDINATORHOST_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
class CSteamID;
|
||||
class IGCSQLQuery;
|
||||
|
||||
class IGameCoordinatorHost
|
||||
{
|
||||
public:
|
||||
virtual bool BSendMessageToClient( AppId_t unAppID, const CSteamID & steamIDTarget, uint32 unMsgType, const void *pubData, uint32 cubData ) = 0;
|
||||
virtual GID_t GenerateGID() = 0;
|
||||
virtual void EmitMessage( const char *pchGroupName, SpewType_t spewType, int iSpewLevel, int iLevelLog, const char *pchMsg ) = 0;
|
||||
virtual void SQLQuery( GID_t gidContextID, IGCSQLQuery *pQuery, int eSchemaCatalog ) = 0;
|
||||
virtual void StartupComplete( bool bSuccess ) = 0;
|
||||
virtual void ShutdownComplete() = 0;
|
||||
virtual EUniverse GetUniverse() = 0;
|
||||
};
|
||||
|
||||
#define GAMECOORDINATORHOST_INTERFACE_VERSION "GAMECOORDINATORHOST002"
|
||||
|
||||
#endif // IGAMECOORDINATORHOST_H
|
||||
@@ -0,0 +1,67 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
#ifndef IGCSQLQUERY_H
|
||||
#define IGCSQLQUERY_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
|
||||
// the type of the parameter
|
||||
enum EGCSQLType
|
||||
{
|
||||
k_EGCSQLTypeInvalid = -1,
|
||||
|
||||
// Variable length types
|
||||
k_EGCSQLType_Blob,
|
||||
k_EGCSQLType_String,
|
||||
|
||||
// fixed length types
|
||||
k_EGCSQLType_int8, // also uint8
|
||||
k_EGCSQLType_int16, // also uint16
|
||||
k_EGCSQLType_int32, // also uint32
|
||||
k_EGCSQLType_int64, // also uint64
|
||||
k_EGCSQLType_float,
|
||||
k_EGCSQLType_double,
|
||||
k_EGCSQLType_Binary, // raw binary data of fixed size (i.e. a C struct).
|
||||
k_EGCSQLType_Image,
|
||||
k_EGCSQLType_bool,
|
||||
};
|
||||
|
||||
class IGCSQLResultSetList;
|
||||
|
||||
class IGCSQLQuery
|
||||
{
|
||||
protected:
|
||||
// call Destroy() instead of deleting this object directly
|
||||
virtual ~IGCSQLQuery() {}
|
||||
|
||||
public:
|
||||
// returns the number of statements in the transaction
|
||||
// represented by this query object
|
||||
virtual uint32 GetStatementCount() = 0;
|
||||
|
||||
// returns a string that represents where in the GC this
|
||||
// query came from. Usually this is FILE_AND_LINE.
|
||||
virtual const char *PchName() = 0;
|
||||
|
||||
// get the null-terminated query string itself
|
||||
virtual const char *PchCommand( uint32 unStatement ) = 0;
|
||||
|
||||
// gets the parameter data
|
||||
virtual uint32 CnParams( uint32 unStatement ) = 0;
|
||||
virtual EGCSQLType EParamType( uint32 unStatement, uint32 uIndex ) = 0;
|
||||
virtual byte *PubParam( uint32 unStatement, uint32 uIndex ) = 0;
|
||||
virtual uint32 CubParam( uint32 unStatement, uint32 uIndex ) = 0;
|
||||
|
||||
// reports the result
|
||||
virtual void SetResults( IGCSQLResultSetList *pResults ) = 0;
|
||||
};
|
||||
|
||||
|
||||
#endif // IGCSQLQUERY_H
|
||||
@@ -0,0 +1,62 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
#ifndef IGCSQLRESULTSETLIST_H
|
||||
#define IGCSQLRESULTSETLIST_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include "igcsqlquery.h"
|
||||
|
||||
enum EGCSQLError
|
||||
{
|
||||
k_EGCSQLErrorNone = 0,
|
||||
k_EGCSQLErrorUnknown,
|
||||
k_EGCSQLErrorBacklog,
|
||||
k_EGCSQLErrorBadQueryParameters,
|
||||
k_EGCSQLErrorConnectionError,
|
||||
k_EGCSQLErrorDataTruncated,
|
||||
k_EGCSQLErrorDeadlockLoser,
|
||||
k_EGCSQLErrorDuplicateKey,
|
||||
k_EGCSQLErrorGenericError,
|
||||
k_EGCSQLErrorNoResultSet,
|
||||
k_EGCSQLErrorSyntaxError,
|
||||
k_EGCSQLErrorTableOrViewNotFound,
|
||||
k_EGCSQLErrorTimeout,
|
||||
k_EGCSQLErrorConstraintViolation,
|
||||
k_EGCSQLErrorNumericValueOutOfRange,
|
||||
k_EGCSQLErrorRollbackFailed,
|
||||
k_EGCSQLErrorColumnNotFound,
|
||||
};
|
||||
|
||||
|
||||
class IGCSQLResultSet
|
||||
{
|
||||
public:
|
||||
virtual uint32 GetColumnCount() = 0;
|
||||
virtual EGCSQLType GetColumnType( uint32 nColumn ) = 0;
|
||||
virtual const char *GetColumnName( uint32 nColumn ) = 0;
|
||||
|
||||
virtual uint32 GetRowCount() = 0;
|
||||
virtual bool GetData( uint32 unRow, uint32 unColumn, uint8 **ppData, uint32 *punSize ) = 0;
|
||||
};
|
||||
|
||||
|
||||
class IGCSQLResultSetList
|
||||
{
|
||||
public:
|
||||
virtual EGCSQLError GetError() = 0;
|
||||
virtual uint32 GetResultSetCount() = 0;
|
||||
virtual IGCSQLResultSet *GetResultSet( uint32 nResultSetIndex ) = 0;
|
||||
virtual uint32 GetRowsAffected( uint32 unWhichStatement ) = 0;
|
||||
virtual void Destroy() = 0;
|
||||
virtual const char *GetErrorText() = 0;
|
||||
};
|
||||
|
||||
|
||||
#endif // IGCSQLRESULTSETLIST_H
|
||||
@@ -0,0 +1,187 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Miscellaneous code
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Tells us whether an account name looks like a VTT account name
|
||||
// (used as an exception for IP-based rate limiting)
|
||||
//-----------------------------------------------------------------------------
|
||||
bool IsVTTAccountName( const char *szAccountName )
|
||||
{
|
||||
const static char *k_szCafe = "valvecafepc";
|
||||
|
||||
if ( 0 == Q_strncmp( szAccountName, k_szCafe, Q_strlen( k_szCafe ) ) )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Random number generation
|
||||
// Use this system for all random number generation. It's very fast, and is
|
||||
// integrated with our automated tests so that we can perform reproducibly "random"
|
||||
// test cases.
|
||||
//-----------------------------------------------------------------------------
|
||||
uint32 g_unRandCur = 0;
|
||||
int g_iunRandMask = 0;
|
||||
|
||||
// k_rgunMask
|
||||
// Set of masks used by our quick and dirty random number generator.
|
||||
const uint32 k_rgunMask[17] =
|
||||
{
|
||||
0x1739a3b0,
|
||||
0xb8907fe1,
|
||||
0x8290d3b7,
|
||||
0x72839cd0,
|
||||
0x242df096,
|
||||
0x3829750b,
|
||||
0x38de7a77,
|
||||
0x72f0924c,
|
||||
0x44783927,
|
||||
0x01925372,
|
||||
0x20902714,
|
||||
0x27585920,
|
||||
0x27890632,
|
||||
0x82910476,
|
||||
0x72906721,
|
||||
0x28798904,
|
||||
0x78592700,
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Quickly generates a vaguely random number.
|
||||
// Output: A vaguely random number.
|
||||
//-----------------------------------------------------------------------------
|
||||
uint32 UNRandFast()
|
||||
{
|
||||
g_iunRandMask++;
|
||||
g_unRandCur += 637429601; // Just add a large prime number (we'll wrap frequently)
|
||||
return ( g_unRandCur ^ k_rgunMask[ g_iunRandMask % 17 ] );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Quickly generates a vaguely random character.
|
||||
// Output: A vaguely random char in the range [32,126].
|
||||
//-----------------------------------------------------------------------------
|
||||
char CHRandFast()
|
||||
{
|
||||
return ( UNRandFast() % 95 ) + 32;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Sets the random number seed (note that we actually break this down
|
||||
// into two parts: g_unRandCur and g_iunRandMask).
|
||||
// Input: ulRandSeed: Value to use as our seed
|
||||
//-----------------------------------------------------------------------------
|
||||
void SetRandSeed( uint64 ulRandSeed )
|
||||
{
|
||||
g_unRandCur = ulRandSeed >> 32;
|
||||
g_iunRandMask = ulRandSeed & 0xffffffff;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns the current random number seed (actually a composite of
|
||||
// g_unRandCur and g_iunRandMask)
|
||||
// Output: Our current 64 bit random number seed.
|
||||
//-----------------------------------------------------------------------------
|
||||
uint64 GetRandSeed()
|
||||
{
|
||||
return ( ( ((uint64)g_unRandCur) << 32 ) + g_iunRandMask );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Quickly fill a memory block with random bytes
|
||||
//-----------------------------------------------------------------------------
|
||||
void RandMem(void *dest, int count)
|
||||
{
|
||||
unsigned char *pDest = (unsigned char *)dest;
|
||||
|
||||
while ( count >= 4 )
|
||||
{
|
||||
*(uint32*)(pDest) = UNRandFast();
|
||||
pDest+=4;
|
||||
count-=4;
|
||||
}
|
||||
|
||||
while ( count > 0 )
|
||||
{
|
||||
*pDest = UNRandFast();
|
||||
pDest++;
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Calculates the percentage of numerator/demoninator, or 0 if
|
||||
// denominator is 0.
|
||||
//-----------------------------------------------------------------------------
|
||||
float SafeCalcPct( uint64 ulNumerator, uint64 ulDenominator )
|
||||
{
|
||||
if ( 0 == ulDenominator )
|
||||
return 0;
|
||||
return ( 100.0f * (float) ulNumerator / (float) ulDenominator );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Common code to reject an operation due to a time backlog.
|
||||
// Does a gradual fade of 0% rejections at the specified threshold
|
||||
// up to 100% at the limit. The gradual fade reduces system oscillations
|
||||
// that could occur if you abruptly stop allowing all operations.
|
||||
// Input: nBacklogCur - the current backlog (in arbitrary units)
|
||||
// nBacklogThreshold - the threshold backlog at which to begin rejecting
|
||||
// nBacklogLimit - hard limit at which to reject 100% of operations
|
||||
// iItem - a monotonically increasing counter of items submitted. Used
|
||||
// to determine which operations are allowed if there is a partial
|
||||
// rejection rate.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool BRejectDueToBacklog( int nBacklogCur, int nBacklogThreshold, int nBacklogLimit, int iItem )
|
||||
{
|
||||
bool bRefuse = false;
|
||||
|
||||
if ( nBacklogCur >= nBacklogLimit )
|
||||
{
|
||||
// if we're over the hard backlog limit, refuse all operations
|
||||
bRefuse = true;
|
||||
}
|
||||
else if ( nBacklogCur >= nBacklogThreshold )
|
||||
{
|
||||
// if we're near the hard backlog limit, start refusing an increasing % of operations,
|
||||
// so we don't snap abruptly in and out of accepting operations and potentially cause oscillations
|
||||
|
||||
// ramp from refusing 0% of operations at the backlog threshold up to 100% at the backlog hard limit
|
||||
|
||||
// calculate refuse % to nearest 10%, to make it easy to mod the item # and get a good distribution
|
||||
float nRefusePctDecile = 10 * (float) ( nBacklogCur - nBacklogThreshold ) /
|
||||
(float) ( nBacklogLimit - nBacklogThreshold );
|
||||
Assert( nRefusePctDecile >= 0.0 );
|
||||
Assert( nRefusePctDecile <= 10.0 );
|
||||
|
||||
// compare the operations submitted count mod 10 to the refusal percent decile to decide if we should
|
||||
// accept or refuse this particular operation
|
||||
if ( ( iItem % 10 ) < nRefusePctDecile )
|
||||
bRefuse = true;
|
||||
}
|
||||
|
||||
return bRefuse;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Defines the head of the CDumpMemFnReg linked list
|
||||
//-----------------------------------------------------------------------------
|
||||
CDumpMemFnReg *CDumpMemFnReg::sm_Head = NULL;
|
||||
@@ -0,0 +1,121 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Miscellaneous platform-specific code
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#ifndef MISC_H
|
||||
#define MISC_H
|
||||
|
||||
// Random number utilities
|
||||
uint32 UNRandFast();
|
||||
char CHRandFast();
|
||||
void SetRandSeed( uint64 ulRandSeed );
|
||||
uint64 GetRandSeed();
|
||||
void RandMem(void *dest, int count);
|
||||
|
||||
bool IsVTTAccountName( const char *szAccountName );
|
||||
|
||||
float SafeCalcPct( uint64 ulNumerator, uint64 ulDenominator );
|
||||
|
||||
bool BRejectDueToBacklog( int nBacklogCur, int nBacklogThreshold, int nBacklogLimit, int iItem );
|
||||
|
||||
#define SAFE_CLOSE_HANDLE( x ) if ( INVALID_HANDLE_VALUE != ( x ) ) { CloseHandle( x ); ( x ) = INVALID_HANDLE_VALUE; }
|
||||
#define SAFE_DELETE( x ) if ( NULL != ( x ) ) { delete ( x ); ( x ) = NULL; }
|
||||
#define SAFE_CLOSE_HCONNECTION( x ) if ( 0 != ( x ) ) { CNet::BClose( ( x ) ); ( x ) = 0; }
|
||||
#define SAFE_RELEASE( x ) if ( NULL != ( x ) ) { ( x )->Release(); x = NULL; }
|
||||
|
||||
#define DECLARE_STEAM_CLASS_SIMPLE( className, baseClassName ) \
|
||||
typedef baseClassName BaseClass; \
|
||||
typedef className ThisClass; \
|
||||
|
||||
#define DECLARE_STEAM_CLASS_NOBASE( className ) \
|
||||
typedef className ThisClass; \
|
||||
|
||||
// Type for our memory output debugging.
|
||||
class IDisplayMemPoolStats
|
||||
{
|
||||
public:
|
||||
virtual void Display( const char *pszClassName, uint64 ulClassSize, uint32 unPoolInstanceCount, uint64 ulPoolMemoryUsage, uint32 unPoolPeakInstanceCount, uint64 ulPoolPeakMemoryUsage ) = 0;
|
||||
};
|
||||
|
||||
// A class for registering functions that will print memory usage information
|
||||
typedef void (*DumpMemFn_t)( IDisplayMemPoolStats * );
|
||||
class CDumpMemFnReg
|
||||
{
|
||||
public:
|
||||
static CDumpMemFnReg *sm_Head;
|
||||
|
||||
CDumpMemFnReg( DumpMemFn_t fn )
|
||||
: m_fn( fn ),
|
||||
m_pNext( sm_Head )
|
||||
{
|
||||
sm_Head = this;
|
||||
}
|
||||
|
||||
DumpMemFn_t m_fn;
|
||||
CDumpMemFnReg *m_pNext;
|
||||
};
|
||||
|
||||
// Helper macros for creating and using CClassMemoryPool on our frequently allocated objects
|
||||
#define DECLARE_CLASS_MEMPOOL( className ) \
|
||||
private: \
|
||||
static CUtlMemoryPool sm_classMemPool; \
|
||||
public: \
|
||||
static void* operator new ( size_t nSize ); \
|
||||
static void* operator new ( size_t nSize, int nBlockUse, const char *pFileName, int nLine ); \
|
||||
static void operator delete ( void *pMem ); \
|
||||
static void DumpMemStats( IDisplayMemPoolStats *pDisplayer ); \
|
||||
|
||||
|
||||
#define IMPLEMENT_CLASS_MEMPOOL( className, initSize, growMode ) \
|
||||
CUtlMemoryPool className::sm_classMemPool( sizeof( className ), ( initSize ), ( growMode ), MEM_ALLOC_CLASSNAME( className ) ); \
|
||||
\
|
||||
void* className::operator new ( size_t nSize ) \
|
||||
{ \
|
||||
if ( nSize != sizeof( className ) ) \
|
||||
{ \
|
||||
EmitError( SPEW_CONSOLE, #className"::operator new() called on wrong size! Expected %llu, Got %llu\n", (uint64)sizeof( className ), (uint64)nSize ); \
|
||||
return NULL; \
|
||||
} \
|
||||
\
|
||||
return sm_classMemPool.Alloc(); \
|
||||
} \
|
||||
\
|
||||
void* className::operator new ( size_t nSize, int nBlockUse, const char *pFileName, int nLine ) \
|
||||
{ \
|
||||
if ( nSize != sizeof( className ) ) \
|
||||
{ \
|
||||
EmitError( SPEW_CONSOLE, #className"::operator new() called on wrong size! Expected %llu, Got %llu\n", (uint64)sizeof( className ), (uint64)nSize ); \
|
||||
return NULL; \
|
||||
} \
|
||||
\
|
||||
return sm_classMemPool.Alloc(); \
|
||||
} \
|
||||
\
|
||||
void className::operator delete ( void *pMem ) \
|
||||
{ \
|
||||
sm_classMemPool.Free( (className *)pMem ); \
|
||||
} \
|
||||
\
|
||||
void className::DumpMemStats( IDisplayMemPoolStats *pDisplayer ) \
|
||||
{ \
|
||||
Assert( pDisplayer ); \
|
||||
pDisplayer->Display \
|
||||
( \
|
||||
#className, \
|
||||
sizeof( className ), \
|
||||
sm_classMemPool.Count(), \
|
||||
sm_classMemPool.Count() * sizeof( className ), \
|
||||
( ( sm_classMemPool.PeakCount() + ( initSize ) ) / ( initSize ) ) * ( initSize ) , \
|
||||
( ( sm_classMemPool.PeakCount() + ( initSize ) ) / ( initSize ) ) * ( initSize ) * sizeof( className ) \
|
||||
); \
|
||||
} \
|
||||
\
|
||||
static CDumpMemFnReg s_##className##RegDumpMemory( &className::DumpMemStats );
|
||||
|
||||
// useful macro for rendering an IP
|
||||
#define iptod(x) ((x)>>24&0xff), ((x)>>16&0xff), ((x)>>8&0xff), ((x)&0xff)
|
||||
|
||||
#endif // MISC_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,187 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Encapsulates real world (wall clock) time
|
||||
//
|
||||
//=============================================================================
|
||||
#ifndef RTIME_H
|
||||
#define RTIME_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef WIN32
|
||||
char* strptime(const char *s, const char *format, struct tm *tm);
|
||||
#endif
|
||||
|
||||
#include <time.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
|
||||
class CSTime;
|
||||
|
||||
// Invalid time values
|
||||
const RTime32 k_RTime32Nil = 0;
|
||||
// time values between Nil and MinValid are available for special constants
|
||||
const RTime32 k_RTime32MinValid = 10;
|
||||
// infinite time value
|
||||
const RTime32 k_RTime32Infinite = 0x7FFFFFFF; //01-18-2038
|
||||
|
||||
// Render buffer size
|
||||
const size_t k_RTimeRenderBufferSize = 25;
|
||||
|
||||
// Flags for component fields. Longer durations must be > shorter ones
|
||||
// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE
|
||||
enum ETimeUnit
|
||||
{
|
||||
k_ETimeUnitNone = 0,
|
||||
k_ETimeUnitSecond = 1,
|
||||
k_ETimeUnitMinute = 2,
|
||||
k_ETimeUnitHour = 3,
|
||||
k_ETimeUnitDay = 4,
|
||||
k_ETimeUnitWeek = 5,
|
||||
k_ETimeUnitMonth = 6,
|
||||
k_ETimeUnitYear = 7,
|
||||
k_ETimeUnitForever
|
||||
};
|
||||
|
||||
// CRTime
|
||||
// This is our primary real time structure.
|
||||
// It offers 1 second resolution beginning on January 1, 1970 (i.e unix time)
|
||||
// This represents wall clock time
|
||||
class CRTime
|
||||
{
|
||||
public:
|
||||
// default constructor initializes to the current time
|
||||
CRTime();
|
||||
CRTime( RTime32 nTime ) : m_nStartTime( nTime ), m_bGMT( false ) {}
|
||||
|
||||
void SetToCurrentTime() { m_nStartTime = sm_nTimeCur; }
|
||||
void SetFromCurrentTime( int dSecOffset ) { m_nStartTime = sm_nTimeCur + dSecOffset; }
|
||||
|
||||
// Amount of seconds that have passed between this CRTime being set and the current wall clock time.
|
||||
int CSecsPassed() const;
|
||||
|
||||
// Time accessors
|
||||
RTime32 GetRTime32() const { return m_nStartTime; }
|
||||
void SetRTime32( RTime32 rTime32 ) { m_nStartTime = rTime32; }
|
||||
|
||||
// Access our cached current time value
|
||||
static void UpdateRealTime();
|
||||
static void SetSystemClock( RTime32 nCurrentTime );
|
||||
static RTime32 RTime32TimeCur() { return sm_nTimeCur; }
|
||||
|
||||
// Render
|
||||
const char *Render( char (&buf)[k_RTimeRenderBufferSize] ) const;
|
||||
static const char *Render( const RTime32 rTime32, char (&buf)[k_RTimeRenderBufferSize] );
|
||||
|
||||
// Return a representation of the current system time
|
||||
static char *PchTimeCur() { return sm_rgchLocalTimeCur; }
|
||||
// Return a representation of the current system date
|
||||
static char *PchDateCur() { return sm_rgchLocalDateCur; }
|
||||
|
||||
// comparisons against other CRTime objects
|
||||
bool operator==( const CRTime &val ) const { return val.m_nStartTime == m_nStartTime; }
|
||||
bool operator<( const CRTime &val ) const { return m_nStartTime < val.m_nStartTime; }
|
||||
bool operator<=( const CRTime &val ) const { return m_nStartTime <= val.m_nStartTime; }
|
||||
bool operator>( const CRTime &val ) const { return m_nStartTime > val.m_nStartTime; }
|
||||
bool operator>=( const CRTime &val ) const { return m_nStartTime >= val.m_nStartTime; }
|
||||
|
||||
// comparisons against RTime32 numbers (to avoid implicit construct/destruct of a temporary CRTime)
|
||||
bool operator==( const RTime32 &val ) const { return m_nStartTime == val; }
|
||||
bool operator<( const RTime32 &val ) const { return m_nStartTime < val; }
|
||||
bool operator<=( const RTime32 &val ) const { return m_nStartTime <= val; }
|
||||
bool operator>( const RTime32 &val ) const { return m_nStartTime > val; }
|
||||
bool operator>=( const RTime32 &val ) const { return m_nStartTime >= val; }
|
||||
|
||||
const CRTime& operator=( const RTime32 &val ) { m_nStartTime = val; return *this; }
|
||||
const CRTime& operator=( const CRTime &val ) { m_nStartTime = val.m_nStartTime; return *this; }
|
||||
const CRTime operator+( const int &nVal ) { return m_nStartTime + nVal; }
|
||||
// Add exactly X seconds to this time
|
||||
const CRTime& operator+=( const int &nVal ) { m_nStartTime += nVal; return *this; }
|
||||
|
||||
// Component Details
|
||||
int GetYear() const;
|
||||
int GetMonth() const; // returns 0..11
|
||||
int GetDayOfYear() const;
|
||||
int GetDayOfMonth() const;
|
||||
int GetDayOfWeek() const;
|
||||
int GetHour() const;
|
||||
int GetMinute() const;
|
||||
int GetSecond() const;
|
||||
int GetISOWeekOfYear() const;
|
||||
|
||||
// Handy references to nearby time boundaries
|
||||
static RTime32 RTime32BeginningOfDay( const RTime32 ); // at 00:00:00
|
||||
static RTime32 RTime32BeginningOfNextDay( const RTime32 ); // at 00:00:00
|
||||
static RTime32 RTime32FirstDayOfMonth( const RTime32 ); // at 00:00:00
|
||||
static RTime32 RTime32LastDayOfMonth( const RTime32 ); // at 00:00:00
|
||||
static RTime32 RTime32FirstDayOfNextMonth( const RTime32 ); // at 00:00:00
|
||||
static RTime32 RTime32LastDayOfNextMonth( const RTime32 ); // at 00:00:00
|
||||
|
||||
static bool BIsLeapYear( int nYear );
|
||||
bool BIsLeapYear() const { return CRTime::BIsLeapYear( GetYear() ); }
|
||||
|
||||
// Parse time using a format string with strptime format
|
||||
static RTime32 RTime32FromFmtString( const char *pchFmt, const char* pchValue );
|
||||
|
||||
// Parse time from string in standard HTTP date format
|
||||
static RTime32 RTime32FromHTTPDateString( const char* pchValue );
|
||||
|
||||
// Parse time from string RFC3339 format (assumes UTC)
|
||||
static RTime32 RTime32FromRFC3339UTCString( const char* pchValue );
|
||||
static const char* RTime32ToRFC3339UTCString( const RTime32 rTime32, char (&buf)[k_RTimeRenderBufferSize] );
|
||||
|
||||
// parse time from string "YYYY-MM-DD hh:mm:ss" or just "YYYY-MM-DD", the ' ',':','-' are optional
|
||||
static RTime32 RTime32FromString( const char* pszValue );
|
||||
|
||||
// turns RTime32 in a string like "YYYY-MM-DD hh:mm:ss"
|
||||
static const char* RTime32ToString( const RTime32 rTime32, char (&buf)[k_RTimeRenderBufferSize], bool bNoPunct = false, bool bOnlyDate = false );
|
||||
|
||||
// turns RTime32 into a string like "Aug 21"
|
||||
static const char* RTime32ToDayString( const RTime32 rTime32, char (&buf)[k_RTimeRenderBufferSize], bool bGMT = false );
|
||||
|
||||
// If the month only has K days, K < N, returns Kth day
|
||||
static RTime32 RTime32NthDayOfMonth( const RTime32, int nDay ); // at 00:00:00
|
||||
|
||||
// Add X months but return the Nth day of that month. If the month only has K days, K < N, returns Kth day.
|
||||
static RTime32 RTime32MonthAddChooseDay( int nNthDayOfMonth, RTime32 rtime32StartDate, int nMonthsToAdd );
|
||||
|
||||
RTime32 GetBeginningOfDay() const { return RTime32BeginningOfDay( m_nStartTime ); }
|
||||
RTime32 GetBeginningOfNextDay() const { return RTime32BeginningOfNextDay( m_nStartTime ); }
|
||||
RTime32 GetFirstDayOfMonth() const { return RTime32FirstDayOfMonth( m_nStartTime ); }
|
||||
RTime32 GetLastDayOfMonth() const { return RTime32LastDayOfMonth( m_nStartTime ); }
|
||||
RTime32 GetFirstDayOfNextMonth() const { return RTime32FirstDayOfNextMonth( m_nStartTime ); }
|
||||
RTime32 GetLastDayOfNextMonth() const { return RTime32LastDayOfNextMonth( m_nStartTime ); }
|
||||
RTime32 GetNthDayOfMonth( int nDay ) const { return RTime32NthDayOfMonth( m_nStartTime, nDay ); }
|
||||
RTime32 MonthAddChooseDay( int nNthDayOfMonth, int nMonthsToAdd ) const { return RTime32MonthAddChooseDay( nNthDayOfMonth, m_nStartTime, nMonthsToAdd ); }
|
||||
|
||||
// Add or subtract N days, weeks, minutes, etc from the current time
|
||||
static RTime32 RTime32DateAdd( const RTime32, int nAmount, ETimeUnit eTimeAmountType );
|
||||
RTime32 DateAdd( int nAmount, ETimeUnit eTimeAmountType ) const { return RTime32DateAdd( m_nStartTime, nAmount, eTimeAmountType ); }
|
||||
|
||||
// Compare two times, and return what the largest time boundary crossed between the two was.
|
||||
// Week boundaries do not line up with Month or Year boundaries, so you must rely on pbWeekChanged for them!
|
||||
static ETimeUnit FindTimeBoundaryCrossings( RTime32 unTime1, RTime32 unTime2, bool *pbWeekChanged );
|
||||
|
||||
void SetToGMT( bool bUseGMT ) { m_bGMT = bUseGMT;}
|
||||
bool BIsGMT() const { return m_bGMT; }
|
||||
|
||||
private:
|
||||
// prevent assignment or copy construction from the server time type
|
||||
const CRTime& operator=( const CSTime &val ) { return *this; }
|
||||
CRTime( const CSTime& ) {}
|
||||
|
||||
RTime32 m_nStartTime; // the time store by this instance (wall clock, in seconds)
|
||||
static RTime32 sm_nTimeCur; // current system wide wall clock time
|
||||
|
||||
static char sm_rgchLocalTimeCur[16]; // string version of time for logging
|
||||
static char sm_rgchLocalDateCur[16]; // string version of date for logging
|
||||
static RTime32 sm_nTimeLastSystemTimeUpdate; // last time we updated above two logging strings
|
||||
|
||||
bool m_bGMT;
|
||||
};
|
||||
|
||||
#endif // RTIME_H
|
||||
@@ -0,0 +1,64 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: interface to the game coordinator for this application
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef ISTEAMGAMECOORDINATOR
|
||||
#define ISTEAMGAMECOORDINATOR
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include "steam/steamtypes.h"
|
||||
#include "steam/steamclientpublic.h"
|
||||
#include "steam/isteamclient.h"
|
||||
|
||||
// list of possible return values from the ISteamGameCoordinator API
|
||||
enum EGCResults
|
||||
{
|
||||
k_EGCResultOK = 0,
|
||||
k_EGCResultNoMessage = 1, // There is no message in the queue
|
||||
k_EGCResultBufferTooSmall = 2, // The buffer is too small for the requested message
|
||||
k_EGCResultNotLoggedOn = 3, // The client is not logged onto Steam
|
||||
k_EGCResultInvalidMessage = 4, // Something was wrong with the message being sent with SendMessage
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Functions for sending and receiving messages from the Game Coordinator
|
||||
// for this application
|
||||
//-----------------------------------------------------------------------------
|
||||
class ISteamGameCoordinator
|
||||
{
|
||||
public:
|
||||
|
||||
// sends a message to the Game Coordinator
|
||||
virtual EGCResults SendMessage( uint32 unMsgType, const void *pubData, uint32 cubData ) = 0;
|
||||
|
||||
// returns true if there is a message waiting from the game coordinator
|
||||
virtual bool IsMessageAvailable( uint32 *pcubMsgSize ) = 0;
|
||||
|
||||
// fills the provided buffer with the first message in the queue and returns k_EGCResultOK or
|
||||
// returns k_EGCResultNoMessage if there is no message waiting. pcubMsgSize is filled with the message size.
|
||||
// If the provided buffer is not large enough to fit the entire message, k_EGCResultBufferTooSmall is returned
|
||||
// and the message remains at the head of the queue.
|
||||
virtual EGCResults RetrieveMessage( uint32 *punMsgType, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize ) = 0;
|
||||
|
||||
};
|
||||
#define STEAMGAMECOORDINATOR_INTERFACE_VERSION "SteamGameCoordinator001"
|
||||
|
||||
// callback notification - A new message is available for reading from the message queue
|
||||
struct GCMessageAvailable_t
|
||||
{
|
||||
enum { k_iCallback = k_iSteamGameCoordinatorCallbacks + 1 };
|
||||
uint32 m_nMessageSize;
|
||||
};
|
||||
|
||||
// callback notification - A message failed to make it to the GC. It may be down temporarily
|
||||
struct GCMessageFailed_t
|
||||
{
|
||||
enum { k_iCallback = k_iSteamGameCoordinatorCallbacks + 2 };
|
||||
};
|
||||
|
||||
#endif // ISTEAMGAMECOORDINATOR
|
||||
@@ -0,0 +1,729 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
#ifdef TF
|
||||
#include "steamcommon.h"
|
||||
#define INCLUDED_STEAM2_USERID_STRUCTS
|
||||
#include "steam/steamclientpublic.h"
|
||||
#endif
|
||||
|
||||
#include "stdafx.h"
|
||||
#ifdef HL1
|
||||
#include "steamcommon.h"
|
||||
#include "steam/steamclientpublic.h"
|
||||
#endif
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
#ifndef UINT64_MAX
|
||||
#define UINT64_MAX ((uint64)-1)
|
||||
#endif
|
||||
|
||||
static const char *DecimalToUint64( const char *pchStr, uint64 unLimit,
|
||||
uint64 *punVal )
|
||||
{
|
||||
const char *pchStart = pchStr;
|
||||
uint64 unVal = 0;
|
||||
|
||||
while ( *pchStr >= '0' && *pchStr <= '9' )
|
||||
{
|
||||
uint64 unNext = unVal * 10;
|
||||
|
||||
if ( unNext < unVal )
|
||||
{
|
||||
// 64-bit overflow.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unVal = unNext + (uint64)( *pchStr - '0' );
|
||||
if ( unVal > unLimit )
|
||||
{
|
||||
// Limit overflow.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pchStr++;
|
||||
}
|
||||
if ( pchStr == pchStart )
|
||||
{
|
||||
// No number at all.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*punVal = unVal;
|
||||
return pchStr;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
// Input : pchSteamID - text representation of a Steam ID
|
||||
//-----------------------------------------------------------------------------
|
||||
CSteamID::CSteamID( const char *pchSteamID, EUniverse eDefaultUniverse /* = k_EUniverseInvalid */ )
|
||||
{
|
||||
SetFromString( pchSteamID, eDefaultUniverse );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Initializes a steam ID from a string
|
||||
// Input : pchSteamID - text representation of a Steam ID
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSteamID::SetFromString( const char *pchSteamID, EUniverse eDefaultUniverse )
|
||||
{
|
||||
uint nAccountID = 0;
|
||||
uint nInstance = 1;
|
||||
EUniverse eUniverse = eDefaultUniverse;
|
||||
EAccountType eAccountType = k_EAccountTypeIndividual;
|
||||
#ifdef DBGFLAG_ASSERT
|
||||
// TF Merge -- Assert is debug-only and we have unused variable warnings on :-/
|
||||
const char *pchSteamIDString = pchSteamID;
|
||||
#endif
|
||||
CSteamID StrictID;
|
||||
|
||||
StrictID.SetFromStringStrict( pchSteamID, eDefaultUniverse );
|
||||
|
||||
if ( *pchSteamID == '[' )
|
||||
pchSteamID++;
|
||||
|
||||
// BUGBUG Rich use the Q_ functions
|
||||
if (*pchSteamID == 'A')
|
||||
{
|
||||
// This is test only
|
||||
pchSteamID++; // skip the A
|
||||
eAccountType = k_EAccountTypeAnonGameServer;
|
||||
if (*pchSteamID == '-' || *pchSteamID == ':')
|
||||
pchSteamID++; // skip the optional - or :
|
||||
|
||||
if ( strchr( pchSteamID, '(' ) )
|
||||
sscanf( strchr( pchSteamID, '(' ), "(%u)", &nInstance );
|
||||
const char *pchColon = strchr( pchSteamID, ':' );
|
||||
if ( pchColon && *pchColon != 0 && strchr( pchColon+1, ':' ))
|
||||
{
|
||||
sscanf( pchSteamID, "%u:%u:%u", (uint*)&eUniverse, &nAccountID, &nInstance );
|
||||
}
|
||||
else if ( pchColon )
|
||||
{
|
||||
sscanf( pchSteamID, "%u:%u", (uint*)&eUniverse, &nAccountID );
|
||||
}
|
||||
else
|
||||
{
|
||||
sscanf( pchSteamID, "%u", &nAccountID );
|
||||
}
|
||||
|
||||
if ( nAccountID == 0 )
|
||||
{
|
||||
// i dont care what number you entered
|
||||
CreateBlankAnonLogon(eUniverse);
|
||||
}
|
||||
else
|
||||
{
|
||||
InstancedSet( nAccountID, nInstance, eUniverse, eAccountType );
|
||||
}
|
||||
// Catch cases where we're allowing sloppy input that we
|
||||
// might not want to allow.
|
||||
AssertMsg1( this->operator==( StrictID ), "Steam ID does not pass strict parsing: '%s'", pchSteamIDString );
|
||||
return;
|
||||
}
|
||||
else if (*pchSteamID == 'G')
|
||||
{
|
||||
pchSteamID++; // skip the G
|
||||
eAccountType = k_EAccountTypeGameServer;
|
||||
if (*pchSteamID == '-' || *pchSteamID == ':')
|
||||
pchSteamID++; // skip the optional - or :
|
||||
}
|
||||
else if (*pchSteamID == 'C')
|
||||
{
|
||||
pchSteamID++; // skip the C
|
||||
eAccountType = k_EAccountTypeContentServer;
|
||||
if (*pchSteamID == '-' || *pchSteamID == ':')
|
||||
pchSteamID++; // skip the optional - or :
|
||||
}
|
||||
else if (*pchSteamID == 'g')
|
||||
{
|
||||
pchSteamID++; // skip the g
|
||||
eAccountType = k_EAccountTypeClan;
|
||||
nInstance = 0;
|
||||
if (*pchSteamID == '-' || *pchSteamID == ':')
|
||||
pchSteamID++; // skip the optional - or :
|
||||
}
|
||||
else if (*pchSteamID == 'c')
|
||||
{
|
||||
pchSteamID++; // skip the c
|
||||
eAccountType = k_EAccountTypeChat;
|
||||
nInstance = k_EChatInstanceFlagClan;
|
||||
if (*pchSteamID == '-' || *pchSteamID == ':')
|
||||
pchSteamID++; // skip the optional - or :
|
||||
}
|
||||
else if (*pchSteamID == 'L')
|
||||
{
|
||||
pchSteamID++; // skip the c
|
||||
eAccountType = k_EAccountTypeChat;
|
||||
nInstance = k_EChatInstanceFlagLobby;
|
||||
if (*pchSteamID == '-' || *pchSteamID == ':')
|
||||
pchSteamID++; // skip the optional - or :
|
||||
}
|
||||
else if (*pchSteamID == 'T')
|
||||
{
|
||||
pchSteamID++; // skip the T
|
||||
eAccountType = k_EAccountTypeChat;
|
||||
nInstance = 0; // Anon chat
|
||||
if (*pchSteamID == '-' || *pchSteamID == ':')
|
||||
pchSteamID++; // skip the optional - or :
|
||||
}
|
||||
else if (*pchSteamID == 'U')
|
||||
{
|
||||
pchSteamID++; // skip the U
|
||||
eAccountType = k_EAccountTypeIndividual;
|
||||
nInstance = 1;
|
||||
if (*pchSteamID == '-' || *pchSteamID == ':')
|
||||
pchSteamID++; // skip the optional - or :
|
||||
}
|
||||
else if (*pchSteamID == 'i')
|
||||
{
|
||||
pchSteamID++; // skip the i
|
||||
eAccountType = k_EAccountTypeInvalid;
|
||||
nInstance = 1;
|
||||
if (*pchSteamID == '-' || *pchSteamID == ':')
|
||||
pchSteamID++; // skip the optional - or :
|
||||
}
|
||||
|
||||
if ( strchr( pchSteamID, ':' ) )
|
||||
{
|
||||
if (*pchSteamID == '[')
|
||||
pchSteamID++; // skip the optional [
|
||||
sscanf( pchSteamID, "%u:%u", (uint*)&eUniverse, &nAccountID );
|
||||
if ( eUniverse == k_EUniverseInvalid )
|
||||
eUniverse = eDefaultUniverse;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64 unVal64 = 0;
|
||||
|
||||
sscanf( pchSteamID, "%llu", &unVal64 );
|
||||
if ( unVal64 > UINT_MAX )
|
||||
{
|
||||
// Assume a full 64-bit Steam ID.
|
||||
SetFromUint64( unVal64 );
|
||||
// Catch cases where we're allowing sloppy input that we
|
||||
// might not want to allow.
|
||||
AssertMsg1( this->operator==( StrictID ), "Steam ID does not pass strict parsing: '%s'", pchSteamIDString );
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
nAccountID = (uint)unVal64;
|
||||
}
|
||||
}
|
||||
|
||||
Assert( (eUniverse > k_EUniverseInvalid) && (eUniverse < k_EUniverseMax) );
|
||||
|
||||
InstancedSet( nAccountID, nInstance, eUniverse, eAccountType );
|
||||
|
||||
// Catch cases where we're allowing sloppy input that we
|
||||
// might not want to allow.
|
||||
AssertMsg1( this->operator==( StrictID ), "Steam ID does not pass strict parsing: '%s'", pchSteamIDString );
|
||||
}
|
||||
|
||||
// SetFromString allows many partially-correct strings, constraining how
|
||||
// we might be able to change things in the future.
|
||||
// SetFromStringStrict requires the exact string forms that we support
|
||||
// and is preferred when the caller knows it's safe to be strict.
|
||||
// Returns whether the string parsed correctly. The ID may
|
||||
// still be invalid even if the string parsed correctly.
|
||||
// If the string didn't parse correctly the ID will always be invalid.
|
||||
bool CSteamID::SetFromStringStrict( const char *pchSteamID, EUniverse eDefaultUniverse )
|
||||
{
|
||||
uint nAccountID = 0;
|
||||
uint nInstance = 1;
|
||||
uint unMaxVal = 2;
|
||||
EUniverse eUniverse = eDefaultUniverse;
|
||||
EAccountType eAccountType = k_EAccountTypeIndividual;
|
||||
char chPrefix;
|
||||
bool bBracket = false;
|
||||
bool bValid = true;
|
||||
uint64 unVal[3];
|
||||
const char *pchEnd;
|
||||
|
||||
// Start invalid.
|
||||
Clear();
|
||||
|
||||
if ( !pchSteamID )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( *pchSteamID == '[' )
|
||||
{
|
||||
pchSteamID++;
|
||||
bBracket = true;
|
||||
}
|
||||
|
||||
chPrefix = *pchSteamID;
|
||||
switch( chPrefix )
|
||||
{
|
||||
case 'A':
|
||||
// This is test only
|
||||
eAccountType = k_EAccountTypeAnonGameServer;
|
||||
unMaxVal = 3;
|
||||
break;
|
||||
|
||||
case 'G':
|
||||
eAccountType = k_EAccountTypeGameServer;
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
eAccountType = k_EAccountTypeContentServer;
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
eAccountType = k_EAccountTypeClan;
|
||||
nInstance = 0;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
eAccountType = k_EAccountTypeChat;
|
||||
nInstance = k_EChatInstanceFlagClan;
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
eAccountType = k_EAccountTypeChat;
|
||||
nInstance = k_EChatInstanceFlagLobby;
|
||||
break;
|
||||
|
||||
case 'T':
|
||||
eAccountType = k_EAccountTypeChat;
|
||||
nInstance = 0; // Anon chat
|
||||
break;
|
||||
|
||||
case 'U':
|
||||
eAccountType = k_EAccountTypeIndividual;
|
||||
nInstance = 1;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
eAccountType = k_EAccountTypeInvalid;
|
||||
nInstance = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
// We're reserving other leading characters so
|
||||
// this should only be the plain-digits case.
|
||||
if (chPrefix < '0' || chPrefix > '9')
|
||||
{
|
||||
bValid = false;
|
||||
}
|
||||
chPrefix = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( chPrefix )
|
||||
{
|
||||
pchSteamID++; // skip the prefix
|
||||
if (*pchSteamID == '-' || *pchSteamID == ':')
|
||||
pchSteamID++; // skip the optional - or :
|
||||
}
|
||||
|
||||
uint unIdx = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
pchEnd = DecimalToUint64( pchSteamID, UINT64_MAX, &unVal[unIdx] );
|
||||
if ( !pchEnd )
|
||||
{
|
||||
bValid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
unIdx++;
|
||||
|
||||
// For 'A' we can have a trailing instance, which must
|
||||
// be the end of the string.
|
||||
if ( *pchEnd == '(' &&
|
||||
chPrefix == 'A' )
|
||||
{
|
||||
if ( unIdx > 2 )
|
||||
{
|
||||
// Two instance IDs provided.
|
||||
bValid = false;
|
||||
}
|
||||
|
||||
pchEnd = DecimalToUint64( pchEnd + 1, k_unSteamAccountInstanceMask, &unVal[2] );
|
||||
if ( !pchEnd ||
|
||||
*pchEnd != ')' )
|
||||
{
|
||||
bValid = false;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
nInstance = (uint)unVal[2];
|
||||
|
||||
pchEnd++;
|
||||
if ( *pchEnd == ':' )
|
||||
{
|
||||
// Not expecting more values.
|
||||
bValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( *pchEnd != ':' )
|
||||
{
|
||||
if ( bBracket )
|
||||
{
|
||||
if ( *pchEnd != ']' ||
|
||||
*(pchEnd + 1) != 0 )
|
||||
{
|
||||
bValid = false;
|
||||
}
|
||||
}
|
||||
else if ( *pchEnd != 0 )
|
||||
{
|
||||
bValid = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ( unIdx >= unMaxVal )
|
||||
{
|
||||
bValid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
pchSteamID = pchEnd + 1;
|
||||
}
|
||||
|
||||
if ( unIdx > 2 )
|
||||
{
|
||||
if ( unVal[2] <= k_unSteamAccountInstanceMask )
|
||||
{
|
||||
nInstance = (uint)unVal[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
bValid = false;
|
||||
}
|
||||
}
|
||||
if ( unIdx > 1 )
|
||||
{
|
||||
if ( unVal[0] >= k_EUniverseInvalid &&
|
||||
unVal[0] < k_EUniverseMax )
|
||||
{
|
||||
eUniverse = (EUniverse)unVal[0];
|
||||
if ( eUniverse == k_EUniverseInvalid )
|
||||
eUniverse = eDefaultUniverse;
|
||||
}
|
||||
else
|
||||
{
|
||||
bValid = false;
|
||||
}
|
||||
|
||||
if ( unVal[1] <= k_unSteamAccountIDMask )
|
||||
{
|
||||
nAccountID = (uint)unVal[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
bValid = false;
|
||||
}
|
||||
}
|
||||
else if ( unIdx > 0 )
|
||||
{
|
||||
if ( unVal[0] <= k_unSteamAccountIDMask )
|
||||
{
|
||||
nAccountID = (uint)unVal[0];
|
||||
}
|
||||
else if ( !chPrefix )
|
||||
{
|
||||
if ( bValid )
|
||||
{
|
||||
SetFromUint64( unVal[0] );
|
||||
}
|
||||
return bValid;
|
||||
}
|
||||
else
|
||||
{
|
||||
bValid = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bValid = false;
|
||||
}
|
||||
|
||||
if ( bValid )
|
||||
{
|
||||
if ( chPrefix == 'A' )
|
||||
{
|
||||
if ( nAccountID == 0 )
|
||||
{
|
||||
// i dont care what number you entered
|
||||
CreateBlankAnonLogon(eUniverse);
|
||||
return bValid;
|
||||
}
|
||||
}
|
||||
|
||||
InstancedSet( nAccountID, nInstance, eUniverse, eAccountType );
|
||||
}
|
||||
|
||||
return bValid;
|
||||
}
|
||||
|
||||
|
||||
#if defined( INCLUDED_STEAM2_USERID_STRUCTS )
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Initializes a steam ID from a Steam2 ID string
|
||||
// Input: pchSteam2ID - Steam2 ID (as a string #:#:#) to convert
|
||||
// eUniverse - universe this ID belongs to
|
||||
// Output: true if successful, false otherwise
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSteamID::SetFromSteam2String( const char *pchSteam2ID, EUniverse eUniverse )
|
||||
{
|
||||
Assert( pchSteam2ID );
|
||||
|
||||
// Convert the Steam2 ID string to a Steam2 ID structure
|
||||
TSteamGlobalUserID steam2ID;
|
||||
steam2ID.m_SteamInstanceID = 0;
|
||||
steam2ID.m_SteamLocalUserID.Split.High32bits = 0;
|
||||
steam2ID.m_SteamLocalUserID.Split.Low32bits = 0;
|
||||
|
||||
const char *pchTSteam2ID = pchSteam2ID;
|
||||
|
||||
// Customer support is fond of entering steam IDs in the following form: STEAM_n:x:y
|
||||
const char *pchOptionalLeadString = "STEAM_";
|
||||
if ( V_strnicmp( pchSteam2ID, pchOptionalLeadString, V_strlen( pchOptionalLeadString ) ) == 0 )
|
||||
pchTSteam2ID = pchSteam2ID + V_strlen( pchOptionalLeadString );
|
||||
|
||||
char cExtraCharCheck = 0;
|
||||
|
||||
int cFieldConverted = sscanf( pchTSteam2ID, "%hu:%u:%u%c", &steam2ID.m_SteamInstanceID,
|
||||
&steam2ID.m_SteamLocalUserID.Split.High32bits, &steam2ID.m_SteamLocalUserID.Split.Low32bits, &cExtraCharCheck );
|
||||
|
||||
// Validate the conversion ... a special case is steam2 instance ID 1 which is reserved for special DoD handling
|
||||
if ( cExtraCharCheck != 0 || cFieldConverted == EOF || cFieldConverted < 2 || ( cFieldConverted < 3 && steam2ID.m_SteamInstanceID != 1 ) )
|
||||
return false;
|
||||
|
||||
// Now convert to steam ID from the Steam2 ID structure
|
||||
SetFromSteam2( &steam2ID, eUniverse );
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Renders the steam ID to a buffer. NOTE: for convenience of calling
|
||||
// code, this code returns a pointer to a static buffer and is NOT thread-safe.
|
||||
// Output: buffer with rendered Steam ID
|
||||
//-----------------------------------------------------------------------------
|
||||
const char * CSteamID::Render() const
|
||||
{
|
||||
// longest length of returned string is k_cBufLen
|
||||
// [A:%u:%u:%u]
|
||||
// %u == 10 * 3 + 6 == 36, plus terminator == 37
|
||||
const int k_cBufLen = 37;
|
||||
|
||||
const int k_cBufs = 4; // # of static bufs to use (so people can compose output with multiple calls to Render() )
|
||||
static char rgchBuf[k_cBufs][k_cBufLen];
|
||||
static int nBuf = 0;
|
||||
char * pchBuf = rgchBuf[nBuf]; // get pointer to current static buf
|
||||
nBuf ++; // use next buffer for next call to this method
|
||||
nBuf %= k_cBufs;
|
||||
|
||||
if ( k_EAccountTypeAnonGameServer == m_steamid.m_comp.m_EAccountType )
|
||||
{
|
||||
V_snprintf( pchBuf, k_cBufLen, "[A:%u:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID, m_steamid.m_comp.m_unAccountInstance );
|
||||
}
|
||||
else if ( k_EAccountTypeGameServer == m_steamid.m_comp.m_EAccountType )
|
||||
{
|
||||
V_snprintf( pchBuf, k_cBufLen, "[G:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID );
|
||||
}
|
||||
else if ( k_EAccountTypeMultiseat == m_steamid.m_comp.m_EAccountType )
|
||||
{
|
||||
V_snprintf( pchBuf, k_cBufLen, "[M:%u:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID, m_steamid.m_comp.m_unAccountInstance );
|
||||
}
|
||||
else if ( k_EAccountTypePending == m_steamid.m_comp.m_EAccountType )
|
||||
{
|
||||
V_snprintf( pchBuf, k_cBufLen, "[P:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID );
|
||||
}
|
||||
else if ( k_EAccountTypeContentServer == m_steamid.m_comp.m_EAccountType )
|
||||
{
|
||||
V_snprintf( pchBuf, k_cBufLen, "[C:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID );
|
||||
}
|
||||
else if ( k_EAccountTypeClan == m_steamid.m_comp.m_EAccountType )
|
||||
{
|
||||
// 'g' for "group"
|
||||
V_snprintf( pchBuf, k_cBufLen, "[g:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID );
|
||||
}
|
||||
else if ( k_EAccountTypeChat == m_steamid.m_comp.m_EAccountType )
|
||||
{
|
||||
if ( m_steamid.m_comp.m_unAccountInstance & k_EChatInstanceFlagClan )
|
||||
{
|
||||
V_snprintf( pchBuf, k_cBufLen, "[c:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID );
|
||||
}
|
||||
else if ( m_steamid.m_comp.m_unAccountInstance & k_EChatInstanceFlagLobby )
|
||||
{
|
||||
V_snprintf( pchBuf, k_cBufLen, "[L:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID );
|
||||
}
|
||||
else // Anon chat
|
||||
{
|
||||
V_snprintf( pchBuf, k_cBufLen, "[T:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID );
|
||||
}
|
||||
}
|
||||
else if ( k_EAccountTypeInvalid == m_steamid.m_comp.m_EAccountType )
|
||||
{
|
||||
V_snprintf( pchBuf, k_cBufLen, "[I:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID );
|
||||
}
|
||||
else if ( k_EAccountTypeIndividual == m_steamid.m_comp.m_EAccountType )
|
||||
{
|
||||
if ( m_steamid.m_comp.m_unAccountInstance != k_unSteamUserDesktopInstance )
|
||||
V_snprintf( pchBuf, k_cBufLen, "[U:%u:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID, m_steamid.m_comp.m_unAccountInstance );
|
||||
else
|
||||
V_snprintf( pchBuf, k_cBufLen, "[U:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID );
|
||||
}
|
||||
else if ( k_EAccountTypeAnonUser == m_steamid.m_comp.m_EAccountType )
|
||||
{
|
||||
V_snprintf( pchBuf, k_cBufLen, "[a:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID );
|
||||
}
|
||||
else
|
||||
{
|
||||
V_snprintf( pchBuf, k_cBufLen, "[i:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID );
|
||||
}
|
||||
return pchBuf;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Renders the passed-in steam ID to a buffer. NOTE: for convenience of calling
|
||||
// code, this code returns a pointer to a static buffer and is NOT thread-safe.
|
||||
// Input: 64-bit representation of Steam ID to render
|
||||
// Output: buffer with rendered Steam ID
|
||||
//-----------------------------------------------------------------------------
|
||||
const char * CSteamID::Render( uint64 ulSteamID )
|
||||
{
|
||||
CSteamID steamID( ulSteamID );
|
||||
return steamID.Render();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: some steamIDs are for internal use only
|
||||
// This is really debug code, but we run with asserts on in retail, so ...
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CSteamID::BValidExternalSteamID() const
|
||||
{
|
||||
if ( m_steamid.m_comp.m_EAccountType == k_EAccountTypePending )
|
||||
return false;
|
||||
if ( m_steamid.m_comp.m_EAccountType != k_EAccountTypeAnonGameServer && m_steamid.m_comp.m_EAccountType != k_EAccountTypeContentServer && m_steamid.m_comp.m_EAccountType != k_EAccountTypeAnonUser )
|
||||
{
|
||||
if ( m_steamid.m_comp.m_unAccountID == 0 && m_steamid.m_comp.m_unAccountInstance == 0 )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef STEAM
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns the matching chat steamID, with the default instance of 0
|
||||
// Input: SteamID, either a Clan or a Chat type
|
||||
// Output: SteamID with account type changed to chat, and the Clan flag set.
|
||||
// If account type was not chat to start with, instance will be set to 0
|
||||
//-----------------------------------------------------------------------------
|
||||
CSteamID ChatIDFromSteamID( const CSteamID &steamID )
|
||||
{
|
||||
if ( steamID.GetEAccountType() == k_EAccountTypeChat )
|
||||
return steamID;
|
||||
|
||||
return ChatIDFromClanID( steamID );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns the matching chat steamID, with the default instance of 0
|
||||
// Input: SteamID, either a Clan type or a Chat type w/ the Clan flag set
|
||||
// Output: SteamID with account type changed to clan.
|
||||
// If account type was not clan to start with, instance will be set to 0
|
||||
//-----------------------------------------------------------------------------
|
||||
CSteamID ClanIDFromSteamID( const CSteamID &steamID )
|
||||
{
|
||||
if ( steamID.GetEAccountType() == k_EAccountTypeClan )
|
||||
return steamID;
|
||||
|
||||
return ClanIDFromChatID( steamID );
|
||||
}
|
||||
|
||||
|
||||
// Asserts steamID type before conversion
|
||||
CSteamID ChatIDFromClanID( const CSteamID &steamIDClan )
|
||||
{
|
||||
Assert( steamIDClan.GetEAccountType() == k_EAccountTypeClan );
|
||||
|
||||
return CSteamID( steamIDClan.GetAccountID(), k_EChatInstanceFlagClan, steamIDClan.GetEUniverse(), k_EAccountTypeChat );
|
||||
}
|
||||
|
||||
|
||||
// Asserts steamID type before conversion
|
||||
CSteamID ClanIDFromChatID( const CSteamID &steamIDChat )
|
||||
{
|
||||
Assert( steamIDChat.GetEAccountType() == k_EAccountTypeChat );
|
||||
Assert( k_EChatInstanceFlagClan & steamIDChat.GetUnAccountInstance() );
|
||||
|
||||
return CSteamID( steamIDChat.GetAccountID(), 0, steamIDChat.GetEUniverse(), k_EAccountTypeClan );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: CGameID "hidden" functions
|
||||
// move these somewhere else maybe
|
||||
//-----------------------------------------------------------------------------
|
||||
CGameID::CGameID( const char *pchGameID )
|
||||
{
|
||||
m_ulGameID = 0;
|
||||
|
||||
sscanf( pchGameID, "%llu", &m_ulGameID );
|
||||
|
||||
switch ( m_gameID.m_nType )
|
||||
{
|
||||
default:
|
||||
AssertMsg( false, "Unknown GameID type" );
|
||||
m_ulGameID = 0;
|
||||
break;
|
||||
case k_EGameIDTypeApp:
|
||||
case k_EGameIDTypeGameMod:
|
||||
case k_EGameIDTypeShortcut:
|
||||
case k_EGameIDTypeP2P:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// renders this Game ID to string
|
||||
const char * CGameID::Render() const
|
||||
{
|
||||
// longest buffer is log10(2**64) == 20 + 1 == 21
|
||||
const int k_cBufLen = 21;
|
||||
|
||||
const int k_cBufs = 4; // # of static bufs to use (so people can compose output with multiple calls to Render() )
|
||||
static char rgchBuf[k_cBufs][k_cBufLen];
|
||||
static int nBuf = 0;
|
||||
char * pchBuf = rgchBuf[nBuf]; // get pointer to current static buf
|
||||
nBuf ++; // use next buffer for next call to this method
|
||||
nBuf %= k_cBufs;
|
||||
|
||||
V_snprintf( pchBuf, k_cBufLen, "%llu", m_ulGameID );
|
||||
|
||||
return pchBuf;
|
||||
}
|
||||
|
||||
// static method to render a uint64 representation of a Game ID to a string
|
||||
const char * CGameID::Render( uint64 ulGameID )
|
||||
{
|
||||
CGameID nGameID( ulGameID );
|
||||
return nGameID.Render();
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,35 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: declares a variety of constants
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
#ifndef T0CONSTANTS_H
|
||||
#define T0CONSTANTS_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// numeric constants to avoid typos with wrong number of zeros
|
||||
//-----------------------------------------------------------------------------
|
||||
const int64 k_nMillion = 1000000;
|
||||
const int64 k_nThousand = 1000;
|
||||
const int64 k_nKiloByte = 1024;
|
||||
const int64 k_nMegabyte = k_nKiloByte * k_nKiloByte;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Timing constants
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const unsigned int k_nSecondsPerHour = 60*60;
|
||||
const unsigned int k_nSecondsPerDay = k_nSecondsPerHour * 24;
|
||||
|
||||
const int k_cSecondsPerMinute = 60;
|
||||
const int k_cSecondsPerHour = k_cSecondsPerMinute * 60;
|
||||
const int k_cSecondsPerDay = k_cSecondsPerHour * 24;
|
||||
const int k_cSecondsPerWeek = k_cSecondsPerDay * 7;
|
||||
const int k_cSecondsPerYear = k_cSecondsPerDay * 365;
|
||||
|
||||
#endif // T0CONSTANTS_H
|
||||
@@ -0,0 +1,35 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "pearsonshash.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
// This is a table of the values 0-255 in pseudo random order for use in our pearsons hash
|
||||
// variant implemented below
|
||||
const unsigned char g_CTHashRandomValues[256] =
|
||||
{
|
||||
131, 184, 146, 42, 124, 142, 226, 76, 8, 135, 215, 116, 228, 49, 144, 231,
|
||||
238, 25, 156, 125, 128, 87, 223, 38, 98, 122, 105, 4, 35, 180, 188, 160,
|
||||
179, 59, 218, 0, 192, 207, 209, 150, 227, 143, 140, 161, 73, 84, 111, 162,
|
||||
239, 74, 210, 195, 15, 225, 104, 221, 245, 12, 72, 47, 109, 187, 40, 178,
|
||||
208, 56, 190, 193, 126, 95, 33, 45, 177, 170, 186, 123, 202, 149, 60, 194,
|
||||
168, 102, 71, 148, 46, 121, 52, 119, 196, 247, 127, 145, 75, 79, 61, 254,
|
||||
9, 44, 23, 211, 18, 175, 251, 130, 203, 108, 85, 167, 29, 250, 138, 182,
|
||||
101, 213, 159, 92, 36, 10, 86, 32, 176, 80, 17, 134, 181, 114, 64, 165,
|
||||
89, 68, 6, 14, 205, 137, 117, 7, 39, 132, 26, 19, 214, 99, 166, 163,
|
||||
69, 174, 157, 100, 201, 118, 2, 28, 235, 236, 139, 244, 70, 20, 155, 82,
|
||||
51, 154, 115, 94, 93, 83, 136, 27, 198, 43, 50, 243, 183, 153, 53, 206,
|
||||
77, 55, 57, 3, 220, 147, 253, 110, 37, 246, 97, 13, 120, 103, 91, 169,
|
||||
58, 11, 133, 22, 152, 189, 222, 151, 141, 88, 224, 1, 48, 191, 249, 173,
|
||||
106, 113, 252, 172, 232, 66, 219, 96, 237, 21, 233, 62, 242, 54, 230, 65,
|
||||
78, 248, 16, 41, 31, 200, 90, 112, 255, 171, 164, 24, 199, 81, 212, 197,
|
||||
185, 67, 5, 234, 30, 129, 216, 63, 204, 158, 217, 229, 107, 240, 241, 34,
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
//======= Copyright © Valve Corporation, All rights reserved. =================
|
||||
//
|
||||
// Public domain MurmurHash3 by Austin Appleby is a very solid general-purpose
|
||||
// hash with a 32-bit output. References:
|
||||
// http://code.google.com/p/smhasher/ (home of MurmurHash3)
|
||||
// https://sites.google.com/site/murmurhash/avalanche
|
||||
// http://www.strchr.com/hash_functions
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include <stdafx.h>
|
||||
#include "murmurhash3.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
uint32 MurmurHash3_32( const void * key, size_t len, uint32 seed, bool bCaselessStringVariant )
|
||||
{
|
||||
const uint8 * data = (const uint8*)key;
|
||||
const ptrdiff_t nblocks = len / 4;
|
||||
uint32 uSourceBitwiseAndMask = 0xDFDFDFDF | ((uint32)bCaselessStringVariant - 1);
|
||||
|
||||
uint32 h1 = seed;
|
||||
|
||||
//----------
|
||||
// body
|
||||
|
||||
const uint32 * blocks = (const uint32 *)(data + nblocks*4);
|
||||
|
||||
for(ptrdiff_t i = -nblocks; i; i++)
|
||||
{
|
||||
uint32 k1 = LittleDWord(blocks[i]);
|
||||
k1 &= uSourceBitwiseAndMask;
|
||||
|
||||
k1 *= 0xcc9e2d51;
|
||||
k1 = (k1 << 15) | (k1 >> 17);
|
||||
k1 *= 0x1b873593;
|
||||
|
||||
h1 ^= k1;
|
||||
h1 = (h1 << 13) | (h1 >> 19);
|
||||
h1 = h1*5+0xe6546b64;
|
||||
}
|
||||
|
||||
//----------
|
||||
// tail
|
||||
|
||||
const uint8 * tail = (const uint8*)(data + nblocks*4);
|
||||
|
||||
uint32 k1 = 0;
|
||||
|
||||
switch(len & 3)
|
||||
{
|
||||
case 3: k1 ^= tail[2] << 16;
|
||||
case 2: k1 ^= tail[1] << 8;
|
||||
case 1: k1 ^= tail[0];
|
||||
k1 &= uSourceBitwiseAndMask;
|
||||
k1 *= 0xcc9e2d51;
|
||||
k1 = (k1 << 15) | (k1 >> 17);
|
||||
k1 *= 0x1b873593;
|
||||
h1 ^= k1;
|
||||
};
|
||||
|
||||
//----------
|
||||
// finalization
|
||||
|
||||
h1 ^= len;
|
||||
|
||||
h1 ^= h1 >> 16;
|
||||
h1 *= 0x85ebca6b;
|
||||
h1 ^= h1 >> 13;
|
||||
h1 *= 0xc2b2ae35;
|
||||
h1 ^= h1 >> 16;
|
||||
|
||||
return h1;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
//======= Copyright © Valve Corporation, All rights reserved. =================
|
||||
//
|
||||
// Public domain MurmurHash3 by Austin Appleby is a very solid general-purpose
|
||||
// hash with a 32-bit output. References:
|
||||
// http://code.google.com/p/smhasher/ (home of MurmurHash3)
|
||||
// https://sites.google.com/site/murmurhash/avalanche
|
||||
// http://www.strchr.com/hash_functions
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef MURMURHASH3_H
|
||||
#define MURMURHASH3_H
|
||||
|
||||
#if defined(_WIN32)
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
uint32 MurmurHash3_32( const void *key, size_t len, uint32 seed, bool bCaselessStringVariant = false );
|
||||
|
||||
inline uint32 MurmurHash3String( const char *pszKey, size_t len )
|
||||
{
|
||||
return MurmurHash3_32( pszKey, len, 1047 /*anything will do for a seed*/, false );
|
||||
}
|
||||
|
||||
inline uint32 MurmurHash3StringCaseless( const char *pszKey, size_t len )
|
||||
{
|
||||
return MurmurHash3_32( pszKey, len, 1047 /*anything will do for a seed*/, true );
|
||||
}
|
||||
|
||||
inline uint32 MurmurHash3String( const char *pszKey )
|
||||
{
|
||||
return MurmurHash3String( pszKey, strlen( pszKey ) );
|
||||
}
|
||||
|
||||
inline uint32 MurmurHash3StringCaseless( const char *pszKey )
|
||||
{
|
||||
return MurmurHash3StringCaseless( pszKey, strlen( pszKey ) );
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline uint32 MurmurHash3Item( const T &item )
|
||||
{
|
||||
return MurmurHash3_32( &item, sizeof(item), 1047 );
|
||||
}
|
||||
|
||||
inline uint32 MurmurHash3Int( uint32 h )
|
||||
{
|
||||
h ^= h >> 16;
|
||||
h *= 0x85ebca6b;
|
||||
h ^= h >> 13;
|
||||
h *= 0xc2b2ae35;
|
||||
h ^= h >> 16;
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
template <>
|
||||
inline uint32 MurmurHash3Item( const uint32 &item )
|
||||
{
|
||||
return MurmurHash3Int( item );
|
||||
}
|
||||
|
||||
template <>
|
||||
inline uint32 MurmurHash3Item( const int32 &item )
|
||||
{
|
||||
return MurmurHash3Int( item );
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct MurmurHash3Functor
|
||||
{
|
||||
typedef uint32 TargetType;
|
||||
TargetType operator()(const T &key) const
|
||||
{
|
||||
return MurmurHash3Item( key );
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct MurmurHash3Functor<char *>
|
||||
{
|
||||
typedef uint32 TargetType;
|
||||
TargetType operator()(const char *key) const
|
||||
{
|
||||
return MurmurHash3String( key );
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct MurmurHash3Functor<const char *>
|
||||
{
|
||||
typedef uint32 TargetType;
|
||||
TargetType operator()(const char *key) const
|
||||
{
|
||||
return MurmurHash3String( key );
|
||||
}
|
||||
};
|
||||
|
||||
#endif // MURMURHASH3_H
|
||||
@@ -0,0 +1,268 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: PearsonsHash.h
|
||||
//
|
||||
// This file contains implementation definitions of 'Pearson's' Hash'
|
||||
// The generic implementation is a template, plus a couple of specializations
|
||||
// for commonly used types.
|
||||
//
|
||||
// Take a look at http://en.wikipedia.org/wiki/Pearson_hashing for more info.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef _PEARSONSHASH_H_
|
||||
#define _PEARSONSHASH_H_
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
extern const unsigned char g_CTHashRandomValues[256] ;
|
||||
|
||||
template<typename T>
|
||||
struct PearsonsHashFunctor
|
||||
{
|
||||
typedef uint32 TargetType ;
|
||||
TargetType operator()(const T& unKey) const
|
||||
{
|
||||
// This is a pearsons hash variant that returns a maximum of 32 bits
|
||||
size_t size = sizeof(T);
|
||||
const uint8 * k = (const uint8 *) &unKey;
|
||||
uint32 byte_one = 0, byte_two = 0, byte_three = 0, byte_four = 0, n;
|
||||
while (size)
|
||||
{
|
||||
--size;
|
||||
n = *k++;
|
||||
byte_one = g_CTHashRandomValues[byte_one ^ n];
|
||||
|
||||
if (size)
|
||||
{
|
||||
--size;
|
||||
n = *k++;
|
||||
byte_two = g_CTHashRandomValues[byte_two ^ n];
|
||||
}
|
||||
else
|
||||
break;
|
||||
|
||||
if (size)
|
||||
{
|
||||
--size;
|
||||
n = *k++;
|
||||
byte_three = g_CTHashRandomValues[byte_three ^ n];
|
||||
}
|
||||
else
|
||||
break;
|
||||
|
||||
if (size)
|
||||
{
|
||||
--size;
|
||||
n = *k++;
|
||||
byte_four = g_CTHashRandomValues[byte_four ^ n];
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
TargetType idx = ( byte_four << 24 ) | ( byte_three << 16 ) | ( byte_two << 8 ) | byte_one;
|
||||
return ( idx );
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// We use this specialization for pointer types - it allows somebody
|
||||
// to define a specialization for some complicated type, and then use a
|
||||
// pointer to that type as a key, and have that automatically go to the
|
||||
// specialization for the complicated type !
|
||||
//
|
||||
template<typename T>
|
||||
struct PearsonsHashFunctor<T*>
|
||||
{
|
||||
typedef uint32 TargetType ;
|
||||
TargetType operator()(const T* key) const
|
||||
{
|
||||
PearsonsHashFunctor<T> functor ;
|
||||
return functor(*key) ;
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// This functor specializes for unsigned 32 bit integers, a commonly used type in Steam.
|
||||
// It should return the exact same result as the unspecialized version on Intel Architecture machines.
|
||||
//
|
||||
template<>
|
||||
struct PearsonsHashFunctor<uint32>
|
||||
{
|
||||
typedef uint32 TargetType ;
|
||||
TargetType operator()(const uint32 unKey) const
|
||||
{
|
||||
uint32 byte_one = g_CTHashRandomValues[(unKey>>0) & 0xff];
|
||||
uint32 byte_two = g_CTHashRandomValues[(unKey>>8) & 0xff];
|
||||
uint32 byte_three = g_CTHashRandomValues[(unKey>>16) & 0xff];
|
||||
uint32 byte_four = g_CTHashRandomValues[(unKey>>24)&0xff];
|
||||
return ( byte_four << 24 ) | ( byte_three << 16 ) | ( byte_two << 8 ) | byte_one;
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// This functor specializes for unsigned 64 bit integers, another commonly used type in Steam.
|
||||
// It should return the exact same result as the unspecialized version on Intel Architecture machines.
|
||||
//
|
||||
template<>
|
||||
struct PearsonsHashFunctor<uint64>
|
||||
{
|
||||
typedef uint32 TargetType ;
|
||||
TargetType operator()(const uint64 unKey) const
|
||||
{
|
||||
//
|
||||
// Note that we pull apart the 64 bits in Intel's endian order.
|
||||
//
|
||||
uint32 n;
|
||||
//
|
||||
// On Intel Machines, to make this return the exact same result as the generic version
|
||||
// we have to go from least significant byte to most significant byte !
|
||||
//
|
||||
n = static_cast<uint32>((unKey >> (0)) & 0xff) ;
|
||||
uint32 byte_one = g_CTHashRandomValues[n];
|
||||
n = static_cast<uint32>((unKey >> (8)) & 0xff) ;
|
||||
uint32 byte_two = g_CTHashRandomValues[n];
|
||||
n = static_cast<uint32>((unKey >> (16)) & 0xff) ;
|
||||
uint32 byte_three = g_CTHashRandomValues[n];
|
||||
n = static_cast<uint32>((unKey >> (24)) & 0xff) ;
|
||||
uint32 byte_four = g_CTHashRandomValues[n];
|
||||
n = static_cast<uint32>((unKey >> (32)) & 0xff) ;
|
||||
byte_one = g_CTHashRandomValues[n ^ byte_one];
|
||||
n = static_cast<uint32>((unKey >> (8+32)) & 0xff) ;
|
||||
byte_two = g_CTHashRandomValues[n ^ byte_two];
|
||||
n = static_cast<uint32>((unKey >> (16+32)) & 0xff) ;
|
||||
byte_three = g_CTHashRandomValues[n ^ byte_three];
|
||||
n = static_cast<uint32>((unKey >> (24+32)) & 0xff) ;
|
||||
byte_four = g_CTHashRandomValues[n ^ byte_four];
|
||||
return ( byte_four << 24 ) | ( byte_three << 16 ) | ( byte_two << 8 ) | byte_one;
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// This functor specializes for C standard NULL terminated strings !
|
||||
// It is setup so that if you had a char array containing a NULL terminated string
|
||||
// and correctly sized, ie char rgch[16] = { "123456789012345" } and a
|
||||
// null terminated string i.e. char *sz = "123456789012345" these will return identical
|
||||
// results, and both include the NULL terminator in the hash calculation.
|
||||
//
|
||||
template<>
|
||||
struct PearsonsHashFunctor<char*>
|
||||
{
|
||||
typedef uint32 TargetType ;
|
||||
TargetType operator()(const char* szKey) const
|
||||
{
|
||||
const uint8 * k = (const uint8 *) szKey ;
|
||||
uint32 byte_one = 0, byte_two = 0, byte_three = 0, byte_four = 0, n;
|
||||
do
|
||||
{
|
||||
n = *k++;
|
||||
byte_one = g_CTHashRandomValues[byte_one ^ n];
|
||||
if (n=='\0')
|
||||
break;
|
||||
|
||||
n = *k++;
|
||||
byte_two = g_CTHashRandomValues[byte_two ^ n];
|
||||
if (n=='\0')
|
||||
break;
|
||||
|
||||
n = *k++;
|
||||
byte_three = g_CTHashRandomValues[byte_three ^ n];
|
||||
if (n=='\0')
|
||||
break;
|
||||
|
||||
n = *k++;
|
||||
byte_four = g_CTHashRandomValues[byte_four ^ n];
|
||||
} while(n!='\0') ;
|
||||
|
||||
TargetType idx = ( byte_four << 24 ) | ( byte_three << 16 ) | ( byte_two << 8 ) | byte_one;
|
||||
return ( idx );
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// This functor compares two objects of a particular type and returns a result
|
||||
// that follows the strcmp/memcmp.
|
||||
// If the type doesn't provide an operator== or operator< then you can provide
|
||||
// a type specific specialization to override this defualt functor !
|
||||
//
|
||||
template<typename T>
|
||||
struct ComparisonFunctor
|
||||
{
|
||||
int operator()(const T &lhs, const T &rhs) const
|
||||
{
|
||||
if( lhs == rhs )
|
||||
return 0 ;
|
||||
else if( lhs < rhs )
|
||||
return -1 ;
|
||||
else
|
||||
return 1 ;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//
|
||||
// I expect people to build Comparison specializations for full types,
|
||||
// not pointer types - this Specialization should allow the C++ compiler
|
||||
// to bind to a specialization for a particular type.
|
||||
// We do not want to compare pointers for equality, and a memcmp() may
|
||||
// not be good for a complicated type !
|
||||
//
|
||||
template<typename T>
|
||||
struct ComparisonFunctor<T*>
|
||||
{
|
||||
int operator()(const T *lhs, const T *rhs) const
|
||||
{
|
||||
ComparisonFunctor<T> functor ;
|
||||
return functor(*lhs, *rhs) ;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ComparisonFunctor<int>
|
||||
{
|
||||
int operator()(const int lhs, const int rhs) const
|
||||
{
|
||||
return lhs-rhs ;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ComparisonFunctor<unsigned int>
|
||||
{
|
||||
int operator()(const unsigned int lhs, const unsigned int rhs) const
|
||||
{
|
||||
return static_cast<int>(lhs) - static_cast<int>(rhs) ;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<>
|
||||
struct ComparisonFunctor<uint64>
|
||||
{
|
||||
int operator()(const uint64 lhs, const uint64 rhs) const
|
||||
{
|
||||
if( lhs < rhs )
|
||||
return -1 ;
|
||||
else if( lhs == rhs )
|
||||
return 0 ;
|
||||
else
|
||||
return 1 ;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ComparisonFunctor<char*>
|
||||
{
|
||||
int operator()(const char * lhs, const char* rhs) const
|
||||
{
|
||||
return Q_strcmp(lhs, rhs) ;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif // _PEARSONSHASH_H_
|
||||
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// The copyright to the contents herein is the property of Valve, L.L.C.
|
||||
// The contents may be used and/or copied only with the written permission of
|
||||
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
|
||||
// the agreement/contract under which the contents have been supplied.
|
||||
//
|
||||
// Purpose:
|
||||
//=============================================================================
|
||||
|
||||
|
||||
//#include "pch_vstdlib.h"
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "tier0/tslist.h"
|
||||
#include "tier0/t0constants.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
static const uint k_cubBytesAllocatedToConsiderFreeingMemory = 5 * k_nMegabyte;
|
||||
static const int k_cBlocksAllocatedToConsiderFreeingMemory = 10;
|
||||
|
||||
typedef TSLNodeBase_t FreeListItem_t;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CThreadSafeMemoryPool::CThreadSafeMemoryPool( int blockSize, int numElements, int growMode )
|
||||
{
|
||||
m_ptslistFreeBlocks = new CTSListBase;
|
||||
|
||||
// round up to the nearest 8-byte boundary
|
||||
if ( blockSize % TSLIST_NODE_ALIGNMENT != 0 )
|
||||
{
|
||||
blockSize += TSLIST_NODE_ALIGNMENT - (blockSize % TSLIST_NODE_ALIGNMENT);
|
||||
}
|
||||
Assert( blockSize % TSLIST_NODE_ALIGNMENT == 0 );
|
||||
Assert( blockSize > sizeof(FreeListItem_t) );
|
||||
m_nGrowMode = growMode;
|
||||
m_cubBlockSize = blockSize;
|
||||
m_nGrowSize = numElements;
|
||||
m_cubAllocated = 0;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Frees the memory contained in the mempool
|
||||
//-----------------------------------------------------------------------------
|
||||
CThreadSafeMemoryPool::~CThreadSafeMemoryPool()
|
||||
{
|
||||
AUTO_LOCK_SPIN_WRITE( m_threadRWLock );
|
||||
FOR_EACH_VEC( m_vecBlockSets, i )
|
||||
{
|
||||
_aligned_free( m_vecBlockSets[i].m_pubBlockSet );
|
||||
}
|
||||
|
||||
delete m_ptslistFreeBlocks;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Frees everything
|
||||
//-----------------------------------------------------------------------------
|
||||
void CThreadSafeMemoryPool::Clear()
|
||||
{
|
||||
AUTO_LOCK_SPIN_WRITE( m_threadRWLock );
|
||||
ClearNoLock();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Frees everything
|
||||
//-----------------------------------------------------------------------------
|
||||
void CThreadSafeMemoryPool::ClearNoLock()
|
||||
{
|
||||
FOR_EACH_VEC( m_vecBlockSets, i )
|
||||
{
|
||||
_aligned_free( m_vecBlockSets[i].m_pubBlockSet );
|
||||
}
|
||||
m_ptslistFreeBlocks->Detach();
|
||||
m_cubAllocated = 0;
|
||||
m_cBlocksInUse = 0;
|
||||
m_vecBlockSets.RemoveAll();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Allocates a single block of memory from the pool.
|
||||
//-----------------------------------------------------------------------------
|
||||
void *CThreadSafeMemoryPool::Alloc()
|
||||
{
|
||||
return Alloc( m_cubBlockSize );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Allocates a single block of memory from the pool.
|
||||
//-----------------------------------------------------------------------------
|
||||
void *CThreadSafeMemoryPool::Alloc( unsigned int amount )
|
||||
{
|
||||
// loop attempting to get memory
|
||||
// there appears to be a case where memory corruption can get this into an infinite loop
|
||||
// normally 1 or 2 attempts are necessary to get a block, so if we hit 1000 we know something is wrong
|
||||
int cAttempts = 1000;
|
||||
while ( --cAttempts )
|
||||
{
|
||||
// pull first from the free list
|
||||
m_threadRWLock.LockForRead();
|
||||
FreeListItem_t *pFreeListItem = m_ptslistFreeBlocks->Pop();
|
||||
if ( pFreeListItem )
|
||||
{
|
||||
m_threadRWLock.UnlockRead();
|
||||
m_cBlocksInUse++;
|
||||
return (void *)pFreeListItem;
|
||||
}
|
||||
m_threadRWLock.UnlockRead();
|
||||
|
||||
// no free items, add a new block
|
||||
|
||||
// switch from a read lock to a write lock
|
||||
AUTO_LOCK_SPIN_WRITE( m_threadRWLock );
|
||||
|
||||
// another thread may have allocated memory; try the free list again if so
|
||||
if ( m_ptslistFreeBlocks->Count() > 0 )
|
||||
continue;
|
||||
|
||||
size_t cubBlob = m_nGrowSize * m_cubBlockSize;
|
||||
if ( m_nGrowMode == GROW_FAST )
|
||||
{
|
||||
cubBlob *= (m_vecBlockSets.Count() + 1);
|
||||
}
|
||||
|
||||
// don't grow if we're told not to
|
||||
if ( m_nGrowMode == GROW_NONE && m_vecBlockSets.Count() == 1 )
|
||||
return NULL;
|
||||
|
||||
// allocate, but we can fail
|
||||
byte *pBlobBase = (byte *)MemAlloc_AllocAligned( cubBlob, TSLIST_NODE_ALIGNMENT /*, (m_nGrowMode == GROW_TIL_YOU_CANT)*/ );
|
||||
if ( !pBlobBase )
|
||||
return NULL;
|
||||
|
||||
byte *pBlobEnd = pBlobBase + cubBlob;
|
||||
// add all the items to the pool
|
||||
for ( byte *pBlob = pBlobBase; pBlob < pBlobEnd; pBlob += m_cubBlockSize )
|
||||
{
|
||||
m_ptslistFreeBlocks->Push( (FreeListItem_t *)pBlob );
|
||||
}
|
||||
|
||||
m_cubAllocated += cubBlob;
|
||||
BlockSet_t blockSet = { pBlobBase, cubBlob };
|
||||
m_vecBlockSets.AddToTail( blockSet );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Frees a block of memory
|
||||
//-----------------------------------------------------------------------------
|
||||
void CThreadSafeMemoryPool::Free( void *pMem )
|
||||
{
|
||||
Free( pMem, m_cubBlockSize );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Frees a block of memory
|
||||
//-----------------------------------------------------------------------------
|
||||
void CThreadSafeMemoryPool::Free( void *pMem, int cubAlloc )
|
||||
{
|
||||
m_threadRWLock.LockForRead();
|
||||
|
||||
// push the item back onto the free list
|
||||
m_ptslistFreeBlocks->Push( (FreeListItem_t *)pMem );
|
||||
m_cBlocksInUse--;
|
||||
|
||||
m_threadRWLock.UnlockRead();
|
||||
|
||||
// if we're completely free, and have too much memory allocated, free some
|
||||
if ( m_cBlocksInUse == 0
|
||||
&& m_cubAllocated >= k_cubBytesAllocatedToConsiderFreeingMemory
|
||||
&& m_vecBlockSets.Count() >= k_cBlocksAllocatedToConsiderFreeingMemory )
|
||||
{
|
||||
AUTO_LOCK_SPIN_WRITE( m_threadRWLock );
|
||||
|
||||
// check again nothing is in use
|
||||
if ( m_cBlocksInUse == 0 )
|
||||
{
|
||||
// free all the blocks
|
||||
ClearNoLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: display
|
||||
//-----------------------------------------------------------------------------
|
||||
void CThreadSafeMemoryPool::PrintStats()
|
||||
{
|
||||
AUTO_LOCK_SPIN_WRITE( m_threadRWLock );
|
||||
int cBlocksInUse = m_cBlocksInUse;
|
||||
Msg( "Block size: %-11s Alloc'd: %8d Num blobs: %5d (%s)\n", Q_pretifymem( m_cubBlockSize, 2, true ),
|
||||
cBlocksInUse, m_vecBlockSets.Count(), Q_pretifymem( m_cubAllocated, 2, true ) );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: data accessor
|
||||
//-----------------------------------------------------------------------------
|
||||
size_t CThreadSafeMemoryPool::CubTotalSize()
|
||||
{
|
||||
return m_cubAllocated;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: data accessor
|
||||
//-----------------------------------------------------------------------------
|
||||
size_t CThreadSafeMemoryPool::CubSizeInUse()
|
||||
{
|
||||
return m_cBlocksInUse * m_cubBlockSize;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: data accessor
|
||||
//-----------------------------------------------------------------------------
|
||||
int CThreadSafeMemoryPool::Count()
|
||||
{
|
||||
return m_cBlocksInUse;
|
||||
}
|
||||
|
||||
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Run a global validation pass on all of our data structures and memory
|
||||
// allocations.
|
||||
// Input: validator - Our global validator object
|
||||
// pchName - Our name (typically a member var in our container)
|
||||
//-----------------------------------------------------------------------------
|
||||
void CThreadSafeMemoryPool::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
AUTO_LOCK_SPIN_WRITE( m_threadRWLock );
|
||||
VALIDATE_SCOPE();
|
||||
FOR_EACH_VEC( m_vecBlockSets, i )
|
||||
{
|
||||
validator.ClaimMemory( MemAlloc_Unalign( m_vecBlockSets[i].m_pubBlockSet ) );
|
||||
}
|
||||
ValidateObj( m_vecBlockSets );
|
||||
validator.ClaimMemory( MemAlloc_Unalign( m_ptslistFreeBlocks ) );
|
||||
}
|
||||
#endif // DBGFLAG_VALIDATE
|
||||
@@ -0,0 +1,170 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// The copyright to the contents herein is the property of Valve, L.L.C.
|
||||
// The contents may be used and/or copied only with the written permission of
|
||||
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
|
||||
// the agreement/contract under which the contents have been supplied.
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $Workfile: $
|
||||
// $Date: $
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// $Log: $
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
#ifndef TSMEMPOOL_H
|
||||
#define TSMEMPOOL_H
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#undef new
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Optimized pool memory allocator
|
||||
//-----------------------------------------------------------------------------
|
||||
class CThreadSafeMemoryPool
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
GROW_NONE=0, // Don't allow new blobs.
|
||||
GROW_FAST=1, // New blob size is numElements * (i+1) (ie: the blocks it allocates get larger and larger each time it allocates one).
|
||||
GROW_SLOW=2, // New blob size is numElements.
|
||||
GROW_TIL_YOU_CANT=3 // GROW_SLOW til alloc fails - then STOP and dont assert!
|
||||
};
|
||||
|
||||
CThreadSafeMemoryPool( int blockSize, int numElements, int growMode = GROW_FAST );
|
||||
~CThreadSafeMemoryPool();
|
||||
|
||||
void *Alloc(); // Allocate the element size you specified in the constructor.
|
||||
void *Alloc( unsigned int cubAlloc );
|
||||
void Free( void *pMem );
|
||||
void Free( void *pMem, int cubAlloc );
|
||||
|
||||
// Frees everything
|
||||
void Clear();
|
||||
|
||||
// display
|
||||
void PrintStats();
|
||||
size_t CubTotalSize();
|
||||
size_t CubSizeInUse();
|
||||
int Count();
|
||||
|
||||
static void * operator new( size_t size )
|
||||
{
|
||||
CThreadSafeMemoryPool *pNode = (CThreadSafeMemoryPool *)MemAlloc_AllocAligned( size, 8, __FILE__, __LINE__
|
||||
#ifdef STEAM
|
||||
, true // new operator
|
||||
#endif
|
||||
);
|
||||
return pNode;
|
||||
}
|
||||
|
||||
static void * operator new( size_t size, int nBlockUse, const char *pFileName, int nLine )
|
||||
{
|
||||
CThreadSafeMemoryPool *pNode = (CThreadSafeMemoryPool *)MemAlloc_AllocAligned( size, 8, pFileName, nLine
|
||||
#ifdef STEAM
|
||||
, true // new operator
|
||||
#endif
|
||||
);
|
||||
return pNode;
|
||||
}
|
||||
|
||||
static void operator delete( void *p)
|
||||
{
|
||||
MemAlloc_FreeAligned( p
|
||||
#ifdef STEAM
|
||||
, true // new operator
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
static void operator delete( void *p, int nBlockUse, const char *pFileName, int nLine )
|
||||
{
|
||||
MemAlloc_FreeAligned( p
|
||||
#ifdef STEAM
|
||||
, true // new operator
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
void Validate( CValidator &validator, const char *pchName ); // Validate our internal structures
|
||||
#endif // DBGFLAG_VALIDATE
|
||||
|
||||
private:
|
||||
// These ain't gonna work
|
||||
static void * operator new[] ( size_t size );
|
||||
static void operator delete [] ( void *p);
|
||||
|
||||
// CThreadSpinRWLock needs 8 byte alignment to work but we new() CThreadSafeMemoryPool
|
||||
// so simply place it at the start of the class to make sure it fits on the 8-byte boundary
|
||||
CThreadSpinRWLock m_threadRWLock;
|
||||
|
||||
int m_nGrowMode;
|
||||
int m_cubBlockSize;
|
||||
int m_nGrowSize;
|
||||
|
||||
void ClearNoLock();
|
||||
|
||||
CInterlockedInt m_cBlocksInUse;
|
||||
size_t m_cubAllocated;
|
||||
|
||||
struct BlockSet_t
|
||||
{
|
||||
byte *m_pubBlockSet;
|
||||
size_t m_cubAllocated;
|
||||
};
|
||||
CUtlVector<BlockSet_t> m_vecBlockSets;
|
||||
|
||||
class CTSListBase *m_ptslistFreeBlocks;
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Wrapper macro to make an allocator that returns particular typed allocations
|
||||
// and construction and destruction of objects.
|
||||
//-----------------------------------------------------------------------------
|
||||
template< class T >
|
||||
class CThreadSafeClassMemoryPool : public CThreadSafeMemoryPool
|
||||
{
|
||||
public:
|
||||
CThreadSafeClassMemoryPool(int numElements, int growMode = GROW_FAST) :
|
||||
CThreadSafeMemoryPool( sizeof(T), numElements, growMode ) {}
|
||||
|
||||
T* Alloc();
|
||||
void Free( T *pMem );
|
||||
};
|
||||
|
||||
|
||||
template< class T >
|
||||
T* CThreadSafeClassMemoryPool<T>::Alloc()
|
||||
{
|
||||
T *pRet = (T*)CThreadSafeMemoryPool::Alloc();
|
||||
if ( pRet )
|
||||
{
|
||||
Construct( pRet );
|
||||
}
|
||||
return pRet;
|
||||
}
|
||||
|
||||
|
||||
template< class T >
|
||||
void CThreadSafeClassMemoryPool<T>::Free(T *pMem)
|
||||
{
|
||||
if ( pMem )
|
||||
{
|
||||
Destruct( pMem );
|
||||
}
|
||||
|
||||
CThreadSafeMemoryPool::Free( pMem );
|
||||
}
|
||||
|
||||
|
||||
#endif // TSMEMPOOL_H
|
||||
@@ -0,0 +1,383 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//
|
||||
//=============================================================================//
|
||||
|
||||
#include <stdafx.h>
|
||||
#include "tier0/t0constants.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
static const int k_cubMemBlockPrefixSize = sizeof(uint32);
|
||||
|
||||
#define ALLOCSIZE_TO_LOOKUP( cubAlloc ) ( (cubAlloc - 1) >> 5 )
|
||||
#define LOOKUP_TO_ALLOCSIZE( iLookup ) ( (iLookup << 5) + 1 )
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: constructor, the sizes in pMemPoolConfig must be in ascending order
|
||||
//-----------------------------------------------------------------------------
|
||||
CThreadSafeMultiMemoryPool::CThreadSafeMultiMemoryPool( const MemPoolConfig_t *pMemPoolConfig, int cnMemPoolConfig, int nGrowMode /*= GROW_FAST*/ )
|
||||
{
|
||||
m_cubReallocedTotal = 0;
|
||||
m_MapRawAllocation.SetLessFunc( DefLessFunc( void * ) );
|
||||
|
||||
for ( int iMemPoolConfig = 0; iMemPoolConfig < cnMemPoolConfig; iMemPoolConfig++ )
|
||||
{
|
||||
MemPoolRecord_t memPoolRecord;
|
||||
// verify that the mem pool sizes are in ascending order
|
||||
Assert( iMemPoolConfig == 0 || ( iMemPoolConfig > 0 && pMemPoolConfig[ iMemPoolConfig - 1 ].m_cubBlockSize < pMemPoolConfig[ iMemPoolConfig].m_cubBlockSize ) );
|
||||
AssertMsg( pMemPoolConfig[ iMemPoolConfig].m_cubBlockSize % 32 == 0, "Mempools sizes must be multiples of 32" );
|
||||
// add an int to the block size so we can note the alloc size
|
||||
memPoolRecord.m_pMemPool = new CThreadSafeMemoryPool( pMemPoolConfig[ iMemPoolConfig ].m_cubBlockSize + k_cubMemBlockPrefixSize,
|
||||
pMemPoolConfig[ iMemPoolConfig ].m_cubDefaultPoolSize, nGrowMode );
|
||||
Assert( memPoolRecord.m_pMemPool );
|
||||
memPoolRecord.m_nBlockSize = pMemPoolConfig[ iMemPoolConfig ].m_cubBlockSize;
|
||||
m_VecMemPool.AddToTail( memPoolRecord );
|
||||
|
||||
// update the largest blocksize
|
||||
m_nBlockSizeMax = MAX( m_nBlockSizeMax, memPoolRecord.m_nBlockSize );
|
||||
}
|
||||
|
||||
// build the lookup table
|
||||
int nLookupMax = m_nBlockSizeMax >> 5;
|
||||
m_VecMemPoolLookup.AddMultipleToTail( nLookupMax );
|
||||
for ( int i = 0; i < nLookupMax; i++ )
|
||||
{
|
||||
uint32 cubAllocSize = LOOKUP_TO_ALLOCSIZE( i );
|
||||
for ( int iMemPool = 0; iMemPool < m_VecMemPool.Count(); iMemPool++ )
|
||||
{
|
||||
if ( m_VecMemPool[iMemPool].m_nBlockSize >= cubAllocSize )
|
||||
{
|
||||
m_VecMemPoolLookup[i] = &m_VecMemPool[iMemPool];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(_DEBUG)
|
||||
// validate the lookup table
|
||||
for ( int i = 1; i < (int)m_nBlockSizeMax; i++ )
|
||||
{
|
||||
for ( int iMemPool = 0; iMemPool < m_VecMemPool.Count(); iMemPool++ )
|
||||
{
|
||||
if ( (int)m_VecMemPool[iMemPool].m_nBlockSize >= i )
|
||||
{
|
||||
AssertMsg( m_VecMemPoolLookup[ALLOCSIZE_TO_LOOKUP(i)] == &m_VecMemPool[iMemPool], "Invalid mempool block size, can't generate lookup table" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // _DEBUG
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CThreadSafeMultiMemoryPool::~CThreadSafeMultiMemoryPool()
|
||||
{
|
||||
AUTO_LOCK( m_mutexRawAllocations );
|
||||
|
||||
for ( int iMemPool = 0; iMemPool < m_VecMemPool.Count(); iMemPool ++ )
|
||||
{
|
||||
delete m_VecMemPool[iMemPool].m_pMemPool;
|
||||
}
|
||||
|
||||
FOR_EACH_MAP_FAST( m_MapRawAllocation, iRawAllocation )
|
||||
{
|
||||
FreePv( m_MapRawAllocation[iRawAllocation].m_pvMem );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Allocates a block of memory at of least nAllocSize bytes
|
||||
// Input : nAllocSize - number of bytes to alloc
|
||||
// Output : pointer to memory alloc'd, NULL on error
|
||||
//-----------------------------------------------------------------------------
|
||||
void *CThreadSafeMultiMemoryPool::Alloc( uint32 cubAllocSize )
|
||||
{
|
||||
if ( cubAllocSize == 0 )
|
||||
return NULL;
|
||||
|
||||
if ( cubAllocSize <= m_nBlockSizeMax )
|
||||
{
|
||||
MemPoolRecord_t *pMemPoolRecord = m_VecMemPoolLookup[ALLOCSIZE_TO_LOOKUP( cubAllocSize )];
|
||||
void *pvMem = pMemPoolRecord->m_pMemPool->Alloc( cubAllocSize + k_cubMemBlockPrefixSize );
|
||||
*(uint32 *)pvMem = cubAllocSize;
|
||||
return ( (char *)pvMem + k_cubMemBlockPrefixSize );
|
||||
}
|
||||
|
||||
|
||||
// can't fit in our mem pools, alloc it in our one off buffer
|
||||
RawAllocation_t rawAllocation;
|
||||
rawAllocation.m_nBlockSize = cubAllocSize;
|
||||
rawAllocation.m_pvMem = PvAlloc( cubAllocSize + k_cubMemBlockPrefixSize );
|
||||
if ( !rawAllocation.m_pvMem )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
*(uint32 *)rawAllocation.m_pvMem = rawAllocation.m_nBlockSize;
|
||||
AUTO_LOCK( m_mutexRawAllocations );
|
||||
m_MapRawAllocation.Insert( rawAllocation.m_pvMem, rawAllocation );
|
||||
return ( (char *)rawAllocation.m_pvMem + k_cubMemBlockPrefixSize );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Free a previously alloc'd block
|
||||
// Input : pMem - memory to free
|
||||
//-----------------------------------------------------------------------------
|
||||
void CThreadSafeMultiMemoryPool::Free( void *pvMem )
|
||||
{
|
||||
if ( !pvMem )
|
||||
return;
|
||||
|
||||
uint32 cubAllocSize = *( (uint32 *)pvMem - 1 );
|
||||
|
||||
if ( cubAllocSize <= m_nBlockSizeMax )
|
||||
{
|
||||
MemPoolRecord_t *pMemPoolRecord = m_VecMemPoolLookup[ALLOCSIZE_TO_LOOKUP( cubAllocSize )];
|
||||
pMemPoolRecord->m_pMemPool->Free( (char *)pvMem - k_cubMemBlockPrefixSize, cubAllocSize + k_cubMemBlockPrefixSize );
|
||||
return;
|
||||
}
|
||||
|
||||
AUTO_LOCK( m_mutexRawAllocations );
|
||||
|
||||
// must have been alloc'd from the raw heap, find it in map
|
||||
void *pvAllocedMem = (char *)pvMem - k_cubMemBlockPrefixSize;
|
||||
int iRawAllocation = m_MapRawAllocation.Find( pvAllocedMem );
|
||||
if ( m_MapRawAllocation.InvalidIndex() == iRawAllocation )
|
||||
{
|
||||
AssertMsg3( false, "CThreadSafeMultiMemoryPool::Free: raw allocation %p (original alloc: %p, %d bytes) not found in allocation map",
|
||||
pvMem, pvAllocedMem, cubAllocSize );
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
FreePv( m_MapRawAllocation[iRawAllocation].m_pvMem );
|
||||
m_MapRawAllocation.RemoveAt( iRawAllocation);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Return the size alloc'd for this block
|
||||
// Input : pMem - memory to report
|
||||
// Output : size in bytes of this memory
|
||||
//-----------------------------------------------------------------------------
|
||||
int CThreadSafeMultiMemoryPool::CubAllocSize(void *pvMem)
|
||||
{
|
||||
if ( !pvMem )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return *(((uint32 *)pvMem) -1);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Frees all previously alloc'd memory
|
||||
//-----------------------------------------------------------------------------
|
||||
void CThreadSafeMultiMemoryPool::Clear()
|
||||
{
|
||||
AUTO_LOCK( m_mutexRawAllocations );
|
||||
|
||||
for ( int iMemPool = 0; iMemPool < m_VecMemPool.Count(); iMemPool++ )
|
||||
{
|
||||
m_VecMemPool[iMemPool].m_pMemPool->Clear();
|
||||
}
|
||||
|
||||
FOR_EACH_MAP_FAST( m_MapRawAllocation, iRawAllocation )
|
||||
{
|
||||
FreePv( m_MapRawAllocation[iRawAllocation].m_pvMem );
|
||||
}
|
||||
m_MapRawAllocation.RemoveAll();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: print to the console info about our storage
|
||||
//-----------------------------------------------------------------------------
|
||||
void CThreadSafeMultiMemoryPool::PrintStats()
|
||||
{
|
||||
for ( int iMemPool= 0; iMemPool < m_VecMemPool.Count(); iMemPool++ )
|
||||
{
|
||||
m_VecMemPool[iMemPool].m_pMemPool->PrintStats();
|
||||
}
|
||||
int cubRawBytesAllocd = 0;
|
||||
AUTO_LOCK( m_mutexRawAllocations );
|
||||
FOR_EACH_MAP_FAST( m_MapRawAllocation, iRawAllocation )
|
||||
{
|
||||
cubRawBytesAllocd += m_MapRawAllocation[iRawAllocation].m_nBlockSize;
|
||||
}
|
||||
Msg( "Raw bytes alloc'd: %s\n", Q_pretifymem( cubRawBytesAllocd, 2, true ) );
|
||||
Msg( "Cumulative bytes re-alloced: %s\n", Q_pretifymem( m_cubReallocedTotal, 2, true ) );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: return the total mem alloced by this pool in MB
|
||||
//-----------------------------------------------------------------------------
|
||||
int CThreadSafeMultiMemoryPool::CMBPoolSize() const
|
||||
{
|
||||
uint64 cubRawBytesAllocd = 0;
|
||||
for ( int iMemPool= 0; iMemPool < m_VecMemPool.Count(); iMemPool++ )
|
||||
{
|
||||
cubRawBytesAllocd += ( m_VecMemPool[iMemPool].m_pMemPool->CubTotalSize() );
|
||||
}
|
||||
AUTO_LOCK( m_mutexRawAllocations );
|
||||
FOR_EACH_MAP_FAST( m_MapRawAllocation, iRawAllocation )
|
||||
{
|
||||
cubRawBytesAllocd += m_MapRawAllocation[iRawAllocation].m_nBlockSize;
|
||||
}
|
||||
|
||||
return ( cubRawBytesAllocd / k_nMegabyte );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: return the total mem alloced by this pool in MB
|
||||
//-----------------------------------------------------------------------------
|
||||
int CThreadSafeMultiMemoryPool::CMBPoolSizeInUse() const
|
||||
{
|
||||
uint64 cubRawBytesAllocd = 0;
|
||||
for ( int iMemPool= 0; iMemPool < m_VecMemPool.Count(); iMemPool++ )
|
||||
{
|
||||
cubRawBytesAllocd += ( m_VecMemPool[iMemPool].m_pMemPool->CubSizeInUse() );
|
||||
}
|
||||
AUTO_LOCK( m_mutexRawAllocations );
|
||||
FOR_EACH_MAP_FAST( m_MapRawAllocation, iRawAllocation )
|
||||
{
|
||||
cubRawBytesAllocd += m_MapRawAllocation[iRawAllocation].m_nBlockSize;
|
||||
}
|
||||
|
||||
return ( cubRawBytesAllocd / k_nMegabyte );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: return number of mempool blocks alloc'd
|
||||
//-----------------------------------------------------------------------------
|
||||
int CThreadSafeMultiMemoryPool::Count()
|
||||
{
|
||||
int cCount = 0;
|
||||
for ( int iMemPool = 0; iMemPool < m_VecMemPool.Count(); iMemPool++ )
|
||||
{
|
||||
cCount += m_VecMemPool[iMemPool].m_pMemPool->Count();
|
||||
}
|
||||
return cCount;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: reallocate an existing block of memory to a new size (and copy the data
|
||||
// Input: pvMem - a pointer to the existing memory
|
||||
// cubAlloc - number of bytes to alloc
|
||||
// Output: returns a pointer to the memory allocated (NULL on error)
|
||||
//-----------------------------------------------------------------------------
|
||||
void *CThreadSafeMultiMemoryPool::ReAlloc( void *pvMem, uint32 cubAlloc )
|
||||
{
|
||||
uint32 cubOldAlloc = CubAllocSize(pvMem);
|
||||
if ( pvMem && cubAlloc <= cubOldAlloc )
|
||||
return pvMem;
|
||||
|
||||
if ( cubOldAlloc > m_nBlockSizeMax )
|
||||
{
|
||||
AUTO_LOCK( m_mutexRawAllocations );
|
||||
// okay, must have been alloc'd from the raw heap, search for it
|
||||
void *pvAllocedMem = (char *)pvMem - k_cubMemBlockPrefixSize;
|
||||
int iRawAllocation = m_MapRawAllocation.Find( pvAllocedMem );
|
||||
if ( m_MapRawAllocation.InvalidIndex() == iRawAllocation )
|
||||
{
|
||||
AssertMsg3( false, "CThreadSafeMultiMemoryPool::ReAlloc: raw allocation %p (original alloc: %p, %d bytes) not found in allocation map",
|
||||
pvMem, pvAllocedMem, cubOldAlloc );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// realloc the memory
|
||||
void *pvNewMem = PvRealloc( pvAllocedMem, cubAlloc + k_cubMemBlockPrefixSize );
|
||||
if ( !pvNewMem )
|
||||
{
|
||||
m_MapRawAllocation.RemoveAt( iRawAllocation );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// update our tracking
|
||||
*(uint32 *)pvNewMem = cubAlloc;
|
||||
if ( pvAllocedMem == pvNewMem )
|
||||
{
|
||||
// if pointer is the same, use the same map entry with the same key (the pointer given to caller)
|
||||
m_MapRawAllocation[iRawAllocation].m_pvMem = pvNewMem;
|
||||
m_MapRawAllocation[iRawAllocation].m_nBlockSize = cubAlloc;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if pointer changed, need to remove the old entry and re-insert with new key
|
||||
m_MapRawAllocation.RemoveAt( iRawAllocation );
|
||||
RawAllocation_t rawAllocation;
|
||||
rawAllocation.m_pvMem = pvNewMem;
|
||||
rawAllocation.m_nBlockSize = cubAlloc;
|
||||
m_MapRawAllocation.Insert( rawAllocation.m_pvMem, rawAllocation );
|
||||
}
|
||||
return ( (char *)pvNewMem + k_cubMemBlockPrefixSize );
|
||||
}
|
||||
else
|
||||
{
|
||||
// see if we can stay in the same block
|
||||
MemPoolRecord_t *pMemPoolRecord = m_VecMemPoolLookup[ALLOCSIZE_TO_LOOKUP( cubOldAlloc )];
|
||||
if ( cubAlloc <= pMemPoolRecord->m_nBlockSize )
|
||||
{
|
||||
// re-assign the size
|
||||
*((uint32 *)pvMem - 1) = cubAlloc;
|
||||
return pvMem;
|
||||
}
|
||||
|
||||
void *pvNewMem = Alloc( cubAlloc );
|
||||
if ( !pvNewMem )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
m_cubReallocedTotal += cubOldAlloc;
|
||||
Q_memcpy( pvNewMem, pvMem, cubOldAlloc );
|
||||
Free( pvMem ); // now free the old memory buffer we had
|
||||
return pvNewMem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Ensure that all of our internal structures are consistent, and
|
||||
// account for all memory that we've allocated.
|
||||
// Input: validator - Our global validator object
|
||||
// pchName - Our name (typically a member var in our container)
|
||||
//-----------------------------------------------------------------------------
|
||||
void CThreadSafeMultiMemoryPool::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
validator.Push( "CThreadSafeMultiMemoryPool", this, pchName );
|
||||
|
||||
ValidateObj( m_VecMemPool );
|
||||
for( int iMemPool = 0; iMemPool < m_VecMemPool.Count(); iMemPool++ )
|
||||
{
|
||||
validator.ClaimMemory_Aligned( m_VecMemPool[iMemPool].m_pMemPool );
|
||||
m_VecMemPool[iMemPool].m_pMemPool->Validate( validator, "m_VecMemPool[iMemPool].m_pMemPool" );
|
||||
}
|
||||
|
||||
AUTO_LOCK( m_mutexRawAllocations );
|
||||
ValidateObj( m_MapRawAllocation );
|
||||
FOR_EACH_MAP_FAST( m_MapRawAllocation, iRawAllocation )
|
||||
{
|
||||
validator.ClaimMemory( m_MapRawAllocation[iRawAllocation].m_pvMem );
|
||||
}
|
||||
|
||||
ValidateObj( m_VecMemPoolLookup );
|
||||
|
||||
validator.Pop();
|
||||
}
|
||||
#endif // DBGFLAG_VALIDATE
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// The copyright to the contents herein is the property of Valve, L.L.C.
|
||||
// The contents may be used and/or copied only with the written permission of
|
||||
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
|
||||
// the agreement/contract under which the contents have been supplied.
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $Workfile: $
|
||||
// $Date: $
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// $Log: $
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
#ifndef TSMULTIMEMPOOL_H
|
||||
#define TSMULTIMEMPOOL_H
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include "tier1/utlmap.h"
|
||||
#include "tier1/mempool.h"
|
||||
#include "tier1/tsmempool.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: A container of a range of mem pool sizes (for network buffers for example)
|
||||
// and a raw alloc capability (for sizes greater than any contained mem pool).
|
||||
//-----------------------------------------------------------------------------
|
||||
class CThreadSafeMultiMemoryPool
|
||||
{
|
||||
public:
|
||||
struct MemPoolConfig_t
|
||||
{
|
||||
uint32 m_cubBlockSize;
|
||||
uint32 m_cubDefaultPoolSize;
|
||||
};
|
||||
|
||||
CThreadSafeMultiMemoryPool( const MemPoolConfig_t *pnBlock, int cnMemPoolConfig, int nGrowMode = UTLMEMORYPOOL_GROW_FAST );
|
||||
~CThreadSafeMultiMemoryPool();
|
||||
|
||||
// Allocate a block of at least nAllocSize bytes
|
||||
void* Alloc( uint32 cubAlloc );
|
||||
// Free a previously alloc'd block
|
||||
void Free(void *pvMem);
|
||||
// ReAllocate a previously allocated block to a new size
|
||||
void* ReAlloc( void *pvMem, uint32 cubAlloc );
|
||||
|
||||
// Frees everything
|
||||
void Clear();
|
||||
|
||||
// alloc size for this bit of alloc'd memory
|
||||
int CubAllocSize( void *pvMem );
|
||||
// prints details about our contained memory
|
||||
void PrintStats();
|
||||
|
||||
// total number of alloc'd elements
|
||||
int Count();
|
||||
|
||||
// Return the total size in MB allocated for this pool
|
||||
int CMBPoolSize() const;
|
||||
// Return the amount of memory in use
|
||||
int CMBPoolSizeInUse() const;
|
||||
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
void Validate( CValidator &validator, const char *pchName ); // Validate our internal structures
|
||||
#endif // DBGFLAG_VALIDATE
|
||||
|
||||
private:
|
||||
struct MemPoolRecord_t
|
||||
{
|
||||
CThreadSafeMemoryPool *m_pMemPool;
|
||||
uint32 m_nBlockSize;
|
||||
};
|
||||
|
||||
CUtlVector<MemPoolRecord_t> m_VecMemPool; // stores our list of mem pools
|
||||
|
||||
uint32 m_nBlockSizeMax;
|
||||
CUtlVector<MemPoolRecord_t *> m_VecMemPoolLookup; // quick lookup table of mempools
|
||||
|
||||
struct RawAllocation_t
|
||||
{
|
||||
void *m_pvMem;
|
||||
uint32 m_nBlockSize;
|
||||
};
|
||||
CUtlMap<void *,RawAllocation_t,int> m_MapRawAllocation; // stores our list of raw alloc'd mem
|
||||
CThreadFastMutex m_mutexRawAllocations;
|
||||
|
||||
uint32 m_cubReallocedTotal;
|
||||
};
|
||||
|
||||
|
||||
#endif // TSMULTIMEMPOOL_H
|
||||
@@ -0,0 +1,693 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. =================//
|
||||
//
|
||||
// Purpose: index-based hash map container well suited for large and growing
|
||||
// datasets. It uses less memory than other hash maps and incrementally
|
||||
// rehashes to reduce reallocation spikes.
|
||||
//
|
||||
//=============================================================================//
|
||||
|
||||
#ifndef UTLHASHMAPLARGE_H
|
||||
#define UTLHASHMAPLARGE_H
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include "tier0/dbg.h"
|
||||
#include "bitvec.h"
|
||||
#include "tier1/murmurhash3.h"
|
||||
|
||||
// fast mod for power of 2 numbers
|
||||
namespace basetypes
|
||||
{
|
||||
template <class T>
|
||||
inline bool IsPowerOf2(T n)
|
||||
{
|
||||
return n > 0 && (n & (n-1)) == 0;
|
||||
}
|
||||
|
||||
template <class T1, class T2>
|
||||
inline T2 ModPowerOf2(T1 a, T2 b)
|
||||
{
|
||||
return T2(a) & (b-1);
|
||||
}
|
||||
}
|
||||
|
||||
// default comparison operator
|
||||
template <typename T>
|
||||
class CDefEquals
|
||||
{
|
||||
public:
|
||||
CDefEquals() {}
|
||||
CDefEquals( int i ) {}
|
||||
inline bool operator()( const T &lhs, const T &rhs ) const { return ( lhs == rhs ); }
|
||||
inline bool operator!() const { return false; }
|
||||
};
|
||||
|
||||
|
||||
// Specialization to compare pointers
|
||||
template <typename T>
|
||||
class CDefEquals<T*>
|
||||
{
|
||||
public:
|
||||
CDefEquals() {}
|
||||
CDefEquals( int i ) {}
|
||||
inline bool operator()( const T *lhs, const T *rhs ) const
|
||||
{
|
||||
if ( lhs == rhs )
|
||||
return true;
|
||||
else if ( NULL == lhs || NULL == rhs )
|
||||
return false;
|
||||
else
|
||||
return ( *lhs == *rhs );
|
||||
}
|
||||
inline bool operator!() const { return false; }
|
||||
};
|
||||
|
||||
|
||||
// Hash specialization for CUtlStrings
|
||||
template<>
|
||||
struct MurmurHash3Functor<CUtlString>
|
||||
{
|
||||
typedef uint32 TargetType ;
|
||||
TargetType operator()(const CUtlString &strKey) const
|
||||
{
|
||||
return MurmurHash3Functor<const char*>()( strKey.String() );
|
||||
}
|
||||
};
|
||||
|
||||
//hash 3 function for a general case sensitive string compares
|
||||
struct MurmurHash3ConstCharPtr
|
||||
{
|
||||
typedef uint32 TargetType ;
|
||||
TargetType operator()( const char* pszKey ) const { return MurmurHash3Functor<const char*>()( pszKey ); }
|
||||
};
|
||||
struct CaseSensitiveStrEquals
|
||||
{
|
||||
bool operator()( const char* pszLhs, const char* pszRhs ) const { return strcmp( pszLhs, pszRhs ) == 0; }
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// Purpose: An associative container. Pretty much identical to CUtlMap without the ability to walk in-order
|
||||
// This container is well suited for large and growing datasets. It uses less
|
||||
// memory than other hash maps and incrementally rehashes to reduce reallocation spikes.
|
||||
// However, it is slower (by about 20%) than CUtlHashTable
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
template <typename K, typename T, typename L = CDefEquals<K>, typename H = MurmurHash3Functor<K> >
|
||||
class CUtlHashMapLarge
|
||||
{
|
||||
public:
|
||||
// This enum exists so that FOR_EACH_MAP and FOR_EACH_MAP_FAST cannot accidentally
|
||||
// be used on a type that is not a CUtlMap. If the code compiles then all is well.
|
||||
// The check for IsUtlMap being true should be free.
|
||||
// Using an enum rather than a static const bool ensures that this trick works even
|
||||
// with optimizations disabled on gcc.
|
||||
enum CompileTimeCheck
|
||||
{
|
||||
IsUtlMap = 1
|
||||
};
|
||||
|
||||
typedef K KeyType_t;
|
||||
typedef T ElemType_t;
|
||||
typedef int IndexType_t;
|
||||
typedef L EqualityFunc_t;
|
||||
typedef H HashFunc_t;
|
||||
|
||||
CUtlHashMapLarge()
|
||||
{
|
||||
m_cElements = 0;
|
||||
m_nMaxElement = 0;
|
||||
m_nMinRehashedBucket = InvalidIndex();
|
||||
m_nMaxRehashedBucket = InvalidIndex();
|
||||
m_iNodeFreeListHead = InvalidIndex();
|
||||
}
|
||||
|
||||
CUtlHashMapLarge( int cElementsExpected )
|
||||
{
|
||||
m_cElements = 0;
|
||||
m_nMaxElement = 0;
|
||||
m_nMinRehashedBucket = InvalidIndex();
|
||||
m_nMaxRehashedBucket = InvalidIndex();
|
||||
m_iNodeFreeListHead = InvalidIndex();
|
||||
EnsureCapacity( cElementsExpected );
|
||||
}
|
||||
|
||||
~CUtlHashMapLarge()
|
||||
{
|
||||
RemoveAll();
|
||||
}
|
||||
|
||||
// gets particular elements
|
||||
ElemType_t & Element( IndexType_t i ) { return m_memNodes.Element( i ).m_elem; }
|
||||
const ElemType_t & Element( IndexType_t i ) const { return m_memNodes.Element( i ).m_elem; }
|
||||
ElemType_t & operator[]( IndexType_t i ) { return m_memNodes.Element( i ).m_elem; }
|
||||
const ElemType_t & operator[]( IndexType_t i ) const { return m_memNodes.Element( i ).m_elem; }
|
||||
KeyType_t & Key( IndexType_t i ) { return m_memNodes.Element( i ).m_key; }
|
||||
const KeyType_t & Key( IndexType_t i ) const { return m_memNodes.Element( i ).m_key; }
|
||||
|
||||
// Num elements
|
||||
IndexType_t Count() const { return m_cElements; }
|
||||
|
||||
// Max "size" of the vector
|
||||
IndexType_t MaxElement() const { return m_nMaxElement; }
|
||||
|
||||
// Checks if a node is valid and in the map
|
||||
bool IsValidIndex( IndexType_t i ) const { return i >= 0 && i < m_nMaxElement && !IsFreeNodeID( m_memNodes[i].m_iNextNode ); }
|
||||
|
||||
// Invalid index
|
||||
static IndexType_t InvalidIndex() { return -1; }
|
||||
|
||||
// Insert method
|
||||
IndexType_t Insert( const KeyType_t &key, const ElemType_t &insert ) { return InsertInternal( key, insert, eInsert_UpdateExisting ); }
|
||||
IndexType_t Insert( const KeyType_t &key ) { return InsertInternal( key, ElemType_t(), eInsert_UpdateExisting ); }
|
||||
IndexType_t InsertWithDupes( const KeyType_t &key, const ElemType_t &insert ) { return InsertInternal( key, insert, eInsert_CreateDupes ); }
|
||||
IndexType_t FindOrInsert( const KeyType_t &key, const ElemType_t &insert ) { return InsertInternal( key, insert, eInsert_LeaveExisting ); }
|
||||
IndexType_t InsertOrReplace( const KeyType_t &key, const ElemType_t &insert ) { return InsertInternal( key, insert, eInsert_UpdateExisting ); }
|
||||
|
||||
|
||||
// Finds an element
|
||||
IndexType_t Find( const KeyType_t &key ) const;
|
||||
|
||||
// has an element
|
||||
bool HasElement( const KeyType_t &key ) const
|
||||
{
|
||||
return Find( key ) != InvalidIndex();
|
||||
}
|
||||
|
||||
void EnsureCapacity( int num );
|
||||
|
||||
void RemoveAt( IndexType_t i );
|
||||
bool Remove( const KeyType_t &key )
|
||||
{
|
||||
int iMap = Find( key );
|
||||
if ( iMap != InvalidIndex() )
|
||||
{
|
||||
RemoveAt( iMap );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void RemoveAll();
|
||||
void Purge();
|
||||
void PurgeAndDeleteElements();
|
||||
|
||||
void Swap( CUtlHashMapLarge<K,T,L,H> &rhs )
|
||||
{
|
||||
m_vecHashBuckets.Swap( rhs.m_vecHashBuckets );
|
||||
V_swap( m_bitsMigratedBuckets, rhs.m_bitsMigratedBuckets );
|
||||
m_memNodes.Swap( rhs.m_memNodes );
|
||||
V_swap( m_iNodeFreeListHead, rhs.m_iNodeFreeListHead );
|
||||
V_swap( m_cElements, rhs.m_cElements );
|
||||
V_swap( m_nMaxElement, rhs.m_nMaxElement );
|
||||
V_swap( m_nMinRehashedBucket, rhs.m_nMinRehashedBucket );
|
||||
V_swap( m_nMaxRehashedBucket, rhs.m_nMaxRehashedBucket );
|
||||
V_swap( m_EqualityFunc, rhs.m_EqualityFunc );
|
||||
V_swap( m_HashFunc, rhs.m_HashFunc );
|
||||
}
|
||||
|
||||
private:
|
||||
enum EInsertPolicy { eInsert_UpdateExisting, eInsert_LeaveExisting, eInsert_CreateDupes };
|
||||
IndexType_t InsertInternal( const KeyType_t &key, const ElemType_t &insert, EInsertPolicy ePolicy );
|
||||
|
||||
inline IndexType_t FreeNodeIDToIndex( IndexType_t i ) const { return (0-i)-3; }
|
||||
inline IndexType_t FreeNodeIndexToID( IndexType_t i ) const { return (-3)-i; }
|
||||
inline bool IsFreeNodeID( IndexType_t i ) const { return i < InvalidIndex(); }
|
||||
|
||||
int FindInBucket( int iBucket, const KeyType_t &key ) const;
|
||||
int AllocNode();
|
||||
void RehashNodesInBucket( int iBucket );
|
||||
void LinkNodeIntoBucket( int iBucket, int iNewNode );
|
||||
void UnlinkNodeFromBucket( int iBucket, int iNewNode );
|
||||
bool RemoveNodeFromBucket( int iBucket, int iNodeToRemove );
|
||||
void IncrementalRehash();
|
||||
|
||||
struct HashBucket_t
|
||||
{
|
||||
IndexType_t m_iNode;
|
||||
};
|
||||
CUtlVector<HashBucket_t> m_vecHashBuckets;
|
||||
|
||||
CLargeVarBitVec m_bitsMigratedBuckets;
|
||||
|
||||
struct Node_t
|
||||
{
|
||||
KeyType_t m_key;
|
||||
ElemType_t m_elem;
|
||||
int m_iNextNode;
|
||||
};
|
||||
CUtlMemory<Node_t> m_memNodes;
|
||||
IndexType_t m_iNodeFreeListHead;
|
||||
|
||||
IndexType_t m_cElements;
|
||||
IndexType_t m_nMaxElement;
|
||||
IndexType_t m_nMinRehashedBucket, m_nMaxRehashedBucket;
|
||||
EqualityFunc_t m_EqualityFunc;
|
||||
HashFunc_t m_HashFunc;
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: inserts an item into the map
|
||||
//-----------------------------------------------------------------------------
|
||||
template <typename K, typename T, typename L, typename H>
|
||||
inline int CUtlHashMapLarge<K,T,L,H>::InsertInternal( const KeyType_t &key, const ElemType_t &insert, EInsertPolicy ePolicy )
|
||||
{
|
||||
// make sure we have room in the hash table
|
||||
if ( m_cElements >= m_vecHashBuckets.Count() )
|
||||
EnsureCapacity( MAX( 16, m_vecHashBuckets.Count() * 2 ) );
|
||||
if ( m_cElements >= m_memNodes.Count() )
|
||||
m_memNodes.Grow( m_memNodes.Count() * 2 );
|
||||
|
||||
// rehash incrementally
|
||||
IncrementalRehash();
|
||||
|
||||
// hash the item
|
||||
uint32 hash = m_HashFunc( key );
|
||||
|
||||
// migrate data forward, if necessary
|
||||
int cBucketsToModAgainst = m_vecHashBuckets.Count() >> 1;
|
||||
int iBucket = basetypes::ModPowerOf2(hash, cBucketsToModAgainst);
|
||||
while ( iBucket >= m_nMinRehashedBucket
|
||||
&& !m_bitsMigratedBuckets.Get( iBucket ) )
|
||||
{
|
||||
RehashNodesInBucket( iBucket );
|
||||
cBucketsToModAgainst >>= 1;
|
||||
iBucket = basetypes::ModPowerOf2(hash, cBucketsToModAgainst);
|
||||
}
|
||||
|
||||
// prevent duplicates if necessary
|
||||
if ( ( ePolicy != eInsert_CreateDupes ) && m_cElements )
|
||||
{
|
||||
// look in the bucket to see if we have a conflict
|
||||
int iBucket2 = basetypes::ModPowerOf2( hash, m_vecHashBuckets.Count() );
|
||||
IndexType_t iNode = FindInBucket( iBucket2, key );
|
||||
if ( iNode != InvalidIndex() )
|
||||
{
|
||||
// a duplicate - update in place (matching CUtlMap)
|
||||
if( ePolicy == eInsert_UpdateExisting )
|
||||
{
|
||||
m_memNodes[iNode].m_elem = insert;
|
||||
}
|
||||
return iNode;
|
||||
}
|
||||
}
|
||||
|
||||
// make an item
|
||||
int iNewNode = AllocNode();
|
||||
m_memNodes[iNewNode].m_iNextNode = InvalidIndex();
|
||||
CopyConstruct( &m_memNodes[iNewNode].m_key, key );
|
||||
CopyConstruct( &m_memNodes[iNewNode].m_elem, insert );
|
||||
|
||||
iBucket = basetypes::ModPowerOf2( hash, m_vecHashBuckets.Count() );
|
||||
|
||||
// link ourselves in
|
||||
// ::OutputDebugStr( CFmtStr( "insert %d into bucket %d\n", key, iBucket ).Access() );
|
||||
LinkNodeIntoBucket( iBucket, iNewNode );
|
||||
|
||||
// return the new node
|
||||
return iNewNode;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: grows the map to fit the specified amount
|
||||
//-----------------------------------------------------------------------------
|
||||
template <typename K, typename T, typename L, typename H>
|
||||
inline void CUtlHashMapLarge<K,T,L,H>::EnsureCapacity( int amount )
|
||||
{
|
||||
m_memNodes.EnsureCapacity( amount );
|
||||
// ::OutputDebugStr( CFmtStr( "grown m_memNodes from %d to %d\n", m_cElements, m_memNodes.Count() ).Access() );
|
||||
|
||||
if ( amount <= m_vecHashBuckets.Count() )
|
||||
return;
|
||||
int cBucketsNeeded = MAX( 16, m_vecHashBuckets.Count() );
|
||||
while ( cBucketsNeeded < amount )
|
||||
cBucketsNeeded *= 2;
|
||||
|
||||
// ::OutputDebugStr( CFmtStr( "grown m_vecHashBuckets from %d to %d\n", m_vecHashBuckets.Count(), cBucketsNeeded ).Access() );
|
||||
|
||||
// grow the hash buckets
|
||||
int grow = cBucketsNeeded - m_vecHashBuckets.Count();
|
||||
int iFirst = m_vecHashBuckets.AddMultipleToTail( grow );
|
||||
// clear all the new data to invalid bits
|
||||
memset( &m_vecHashBuckets[iFirst], 0xFFFFFFFF, grow*sizeof(m_vecHashBuckets[iFirst]) );
|
||||
Assert( basetypes::IsPowerOf2( m_vecHashBuckets.Count() ) );
|
||||
|
||||
// we'll have to rehash, all the buckets that existed before growth
|
||||
m_nMinRehashedBucket = 0;
|
||||
m_nMaxRehashedBucket = iFirst;
|
||||
if ( m_cElements > 0 )
|
||||
{
|
||||
// remove all the current bits
|
||||
m_bitsMigratedBuckets.Resize( 0 );
|
||||
// re-add new bits; these will all be reset to 0
|
||||
m_bitsMigratedBuckets.Resize( m_vecHashBuckets.Count() );
|
||||
}
|
||||
else
|
||||
{
|
||||
// no elements - no rehashing
|
||||
m_nMinRehashedBucket = m_vecHashBuckets.Count();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: gets a new node, from the free list if possible
|
||||
//-----------------------------------------------------------------------------
|
||||
template <typename K, typename T, typename L, typename H>
|
||||
inline int CUtlHashMapLarge<K,T,L,H>::AllocNode()
|
||||
{
|
||||
// if we're out of free elements, get the max
|
||||
if ( m_cElements == m_nMaxElement )
|
||||
{
|
||||
m_cElements++;
|
||||
return m_nMaxElement++;
|
||||
}
|
||||
|
||||
// pull from the free list
|
||||
Assert( m_iNodeFreeListHead != InvalidIndex() );
|
||||
int iNewNode = m_iNodeFreeListHead;
|
||||
m_iNodeFreeListHead = FreeNodeIDToIndex( m_memNodes[iNewNode].m_iNextNode );
|
||||
m_cElements++;
|
||||
return iNewNode;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: takes a bucket of nodes and re-hashes them into a more optimal bucket
|
||||
//-----------------------------------------------------------------------------
|
||||
template <typename K, typename T, typename L, typename H>
|
||||
inline void CUtlHashMapLarge<K,T,L,H>::RehashNodesInBucket( int iBucketSrc )
|
||||
{
|
||||
// mark us as migrated
|
||||
m_bitsMigratedBuckets.Set( iBucketSrc );
|
||||
|
||||
// walk the list of items, re-hashing them
|
||||
IndexType_t iNode = m_vecHashBuckets[iBucketSrc].m_iNode;
|
||||
while ( iNode != InvalidIndex() )
|
||||
{
|
||||
IndexType_t iNodeNext = m_memNodes[iNode].m_iNextNode;
|
||||
Assert( iNodeNext != iNode );
|
||||
|
||||
// work out where the node should go
|
||||
const KeyType_t &key = m_memNodes[iNode].m_key;
|
||||
uint32 hash = m_HashFunc( key );
|
||||
int iBucketDest = basetypes::ModPowerOf2( hash, m_vecHashBuckets.Count() );
|
||||
|
||||
// if the hash bucket has changed, move it
|
||||
if ( iBucketDest != iBucketSrc )
|
||||
{
|
||||
// ::OutputDebugStr( CFmtStr( "moved key %d from bucket %d to %d\n", key, iBucketSrc, iBucketDest ).Access() );
|
||||
|
||||
// remove from this bucket list
|
||||
UnlinkNodeFromBucket( iBucketSrc, iNode );
|
||||
|
||||
// link into new bucket list
|
||||
LinkNodeIntoBucket( iBucketDest, iNode );
|
||||
}
|
||||
iNode = iNodeNext;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: searches for an item by key, returning the index handle
|
||||
//-----------------------------------------------------------------------------
|
||||
template <typename K, typename T, typename L, typename H>
|
||||
inline int CUtlHashMapLarge<K,T,L,H>::Find( const KeyType_t &key ) const
|
||||
{
|
||||
if ( m_cElements == 0 )
|
||||
return InvalidIndex();
|
||||
|
||||
// hash the item
|
||||
uint32 hash = m_HashFunc( key );
|
||||
|
||||
// find the bucket
|
||||
int cBucketsToModAgainst = m_vecHashBuckets.Count();
|
||||
int iBucket = basetypes::ModPowerOf2( hash, cBucketsToModAgainst );
|
||||
|
||||
// look in the bucket for the item
|
||||
int iNode = FindInBucket( iBucket, key );
|
||||
if ( iNode != InvalidIndex() )
|
||||
return iNode;
|
||||
|
||||
// not found? we may have to look in older buckets
|
||||
cBucketsToModAgainst >>= 1;
|
||||
while ( cBucketsToModAgainst >= m_nMinRehashedBucket )
|
||||
{
|
||||
iBucket = basetypes::ModPowerOf2( hash, cBucketsToModAgainst );
|
||||
|
||||
if ( !m_bitsMigratedBuckets.Get( iBucket ) )
|
||||
{
|
||||
int iNode2 = FindInBucket( iBucket, key );
|
||||
if ( iNode2 != InvalidIndex() )
|
||||
return iNode2;
|
||||
}
|
||||
|
||||
cBucketsToModAgainst >>= 1;
|
||||
}
|
||||
|
||||
return InvalidIndex();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: searches for an item by key, returning the index handle
|
||||
//-----------------------------------------------------------------------------
|
||||
template <typename K, typename T, typename L, typename H>
|
||||
inline int CUtlHashMapLarge<K,T,L,H>::FindInBucket( int iBucket, const KeyType_t &key ) const
|
||||
{
|
||||
if ( m_vecHashBuckets[iBucket].m_iNode != InvalidIndex() )
|
||||
{
|
||||
IndexType_t iNode = m_vecHashBuckets[iBucket].m_iNode;
|
||||
Assert( iNode < m_nMaxElement );
|
||||
while ( iNode != InvalidIndex() )
|
||||
{
|
||||
// equality check
|
||||
if ( m_EqualityFunc( key, m_memNodes[iNode].m_key ) )
|
||||
return iNode;
|
||||
|
||||
iNode = m_memNodes[iNode].m_iNextNode;
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidIndex();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: links a node into a bucket
|
||||
//-----------------------------------------------------------------------------
|
||||
template <typename K, typename T, typename L, typename H>
|
||||
void CUtlHashMapLarge<K,T,L,H>::LinkNodeIntoBucket( int iBucket, int iNewNode )
|
||||
{
|
||||
// add into the start of the bucket's list
|
||||
m_memNodes[iNewNode].m_iNextNode = m_vecHashBuckets[iBucket].m_iNode;
|
||||
m_vecHashBuckets[iBucket].m_iNode = iNewNode;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: unlinks a node from the bucket
|
||||
//-----------------------------------------------------------------------------
|
||||
template <typename K, typename T, typename L, typename H>
|
||||
void CUtlHashMapLarge<K,T,L,H>::UnlinkNodeFromBucket( int iBucket, int iNodeToUnlink )
|
||||
{
|
||||
int iNodeNext = m_memNodes[iNodeToUnlink].m_iNextNode;
|
||||
|
||||
// if it's the first node, just update the bucket to point to the new place
|
||||
int iNode = m_vecHashBuckets[iBucket].m_iNode;
|
||||
if ( iNode == iNodeToUnlink )
|
||||
{
|
||||
m_vecHashBuckets[iBucket].m_iNode = iNodeNext;
|
||||
return;
|
||||
}
|
||||
|
||||
// walk the list to find where
|
||||
while ( iNode != InvalidIndex() )
|
||||
{
|
||||
if ( m_memNodes[iNode].m_iNextNode == iNodeToUnlink )
|
||||
{
|
||||
m_memNodes[iNode].m_iNextNode = iNodeNext;
|
||||
return;
|
||||
}
|
||||
iNode = m_memNodes[iNode].m_iNextNode;
|
||||
}
|
||||
|
||||
// should always be valid to unlink
|
||||
Assert( false );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: removes a single item from the map
|
||||
//-----------------------------------------------------------------------------
|
||||
template <typename K, typename T, typename L, typename H>
|
||||
inline void CUtlHashMapLarge<K,T,L,H>::RemoveAt( IndexType_t i )
|
||||
{
|
||||
if ( !IsValidIndex( i ) )
|
||||
{
|
||||
Assert( false );
|
||||
return;
|
||||
}
|
||||
|
||||
// unfortunately, we have to re-hash to find which bucket we're in
|
||||
uint32 hash = m_HashFunc( m_memNodes[i].m_key );
|
||||
int cBucketsToModAgainst = m_vecHashBuckets.Count();
|
||||
int iBucket = basetypes::ModPowerOf2( hash, cBucketsToModAgainst );
|
||||
if ( RemoveNodeFromBucket( iBucket, i ) )
|
||||
return;
|
||||
|
||||
// wasn't found; look in older buckets
|
||||
cBucketsToModAgainst >>= 1;
|
||||
while ( cBucketsToModAgainst >= m_nMinRehashedBucket )
|
||||
{
|
||||
iBucket = basetypes::ModPowerOf2( hash, cBucketsToModAgainst );
|
||||
|
||||
if ( !m_bitsMigratedBuckets.Get( iBucket ) )
|
||||
{
|
||||
if ( RemoveNodeFromBucket( iBucket, i ) )
|
||||
return;
|
||||
}
|
||||
|
||||
cBucketsToModAgainst >>= 1;
|
||||
}
|
||||
|
||||
// never found, container is busted
|
||||
Assert( false );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: removes a node from the bucket, return true if it was found
|
||||
//-----------------------------------------------------------------------------
|
||||
template <typename K, typename T, typename L, typename H>
|
||||
inline bool CUtlHashMapLarge<K,T,L,H>::RemoveNodeFromBucket( IndexType_t iBucket, int iNodeToRemove )
|
||||
{
|
||||
IndexType_t iNode = m_vecHashBuckets[iBucket].m_iNode;
|
||||
while ( iNode != InvalidIndex() )
|
||||
{
|
||||
if ( iNodeToRemove == iNode )
|
||||
{
|
||||
// found it, remove
|
||||
UnlinkNodeFromBucket( iBucket, iNodeToRemove );
|
||||
Destruct( &m_memNodes[iNode].m_key );
|
||||
Destruct( &m_memNodes[iNode].m_elem );
|
||||
|
||||
// link into free list
|
||||
m_memNodes[iNode].m_iNextNode = FreeNodeIndexToID( m_iNodeFreeListHead );
|
||||
m_iNodeFreeListHead = iNode;
|
||||
m_cElements--;
|
||||
if ( m_cElements == 0 )
|
||||
{
|
||||
m_nMinRehashedBucket = m_vecHashBuckets.Count();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
iNode = m_memNodes[iNode].m_iNextNode;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: removes all items from the hash map
|
||||
//-----------------------------------------------------------------------------
|
||||
template <typename K, typename T, typename L, typename H>
|
||||
inline void CUtlHashMapLarge<K,T,L,H>::RemoveAll()
|
||||
{
|
||||
FOR_EACH_MAP_FAST( *this, i )
|
||||
{
|
||||
Destruct( &m_memNodes[i].m_key );
|
||||
Destruct( &m_memNodes[i].m_elem );
|
||||
}
|
||||
|
||||
m_cElements = 0;
|
||||
m_nMaxElement = 0;
|
||||
m_iNodeFreeListHead = InvalidIndex();
|
||||
m_nMinRehashedBucket = m_vecHashBuckets.Count();
|
||||
m_nMaxRehashedBucket = InvalidIndex();
|
||||
m_bitsMigratedBuckets.Resize( 0 );
|
||||
memset( m_vecHashBuckets.Base(), 0xFF, m_vecHashBuckets.Count() * sizeof(HashBucket_t) );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: removes all items from the hash map and releases memory
|
||||
//-----------------------------------------------------------------------------
|
||||
template <typename K, typename T, typename L, typename H>
|
||||
inline void CUtlHashMapLarge<K,T,L,H>::Purge()
|
||||
{
|
||||
FOR_EACH_MAP_FAST( *this, i )
|
||||
{
|
||||
Destruct( &m_memNodes[i].m_key );
|
||||
Destruct( &m_memNodes[i].m_elem );
|
||||
}
|
||||
|
||||
m_cElements = 0;
|
||||
m_nMaxElement = 0;
|
||||
m_iNodeFreeListHead = InvalidIndex();
|
||||
m_nMinRehashedBucket = InvalidIndex();
|
||||
m_nMaxRehashedBucket = InvalidIndex();
|
||||
|
||||
m_bitsMigratedBuckets.Resize( 0 );
|
||||
m_memNodes.Purge();
|
||||
m_vecHashBuckets.Purge();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: removes and deletes all items from the hash map and releases memory
|
||||
//-----------------------------------------------------------------------------
|
||||
template <typename K, typename T, typename L, typename H>
|
||||
inline void CUtlHashMapLarge<K,T,L,H>::PurgeAndDeleteElements()
|
||||
{
|
||||
FOR_EACH_MAP_FAST( *this, i )
|
||||
{
|
||||
delete this->Element( i );
|
||||
}
|
||||
|
||||
Purge();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: rehashes buckets
|
||||
//-----------------------------------------------------------------------------
|
||||
template <typename K, typename T, typename L, typename H>
|
||||
inline void CUtlHashMapLarge<K,T,L,H>::IncrementalRehash()
|
||||
{
|
||||
if ( m_nMinRehashedBucket < m_nMaxRehashedBucket )
|
||||
{
|
||||
while ( m_nMinRehashedBucket < m_nMaxRehashedBucket )
|
||||
{
|
||||
// see if the bucket needs rehashing
|
||||
if ( m_vecHashBuckets[m_nMinRehashedBucket].m_iNode != InvalidIndex()
|
||||
&& !m_bitsMigratedBuckets.Get(m_nMinRehashedBucket) )
|
||||
{
|
||||
// rehash this bucket
|
||||
RehashNodesInBucket( m_nMinRehashedBucket );
|
||||
// only actively do one - don't want to do it too fast since we may be on a rapid growth path
|
||||
++m_nMinRehashedBucket;
|
||||
break;
|
||||
}
|
||||
|
||||
// nothing to rehash in that bucket - increment and look again
|
||||
++m_nMinRehashedBucket;
|
||||
}
|
||||
|
||||
if ( m_nMinRehashedBucket >= m_nMaxRehashedBucket )
|
||||
{
|
||||
// we're done; don't need any bits anymore
|
||||
m_nMinRehashedBucket = m_vecHashBuckets.Count();
|
||||
m_nMaxRehashedBucket = InvalidIndex();
|
||||
m_bitsMigratedBuckets.Resize( 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif // UTLHASHMAPLARGE_H
|
||||
@@ -0,0 +1,860 @@
|
||||
//====== Copyright 1996-2010, Valve Corporation, All rights reserved. =======
|
||||
//
|
||||
// Purpose: The file defines our Google Protocol Buffers which are used in over
|
||||
// the wire messages between servers as well as between clients and servers.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
// We care more about speed than code size
|
||||
option optimize_for = SPEED;
|
||||
|
||||
// We don't use the service generation functionality
|
||||
option cc_generic_services = false;
|
||||
|
||||
|
||||
//
|
||||
// STYLE NOTES:
|
||||
//
|
||||
// Use CamelCase CMsgMyMessageName style names for messages.
|
||||
//
|
||||
// Use lowercase _ delimited names like my_steam_id for field names, this is non-standard for Steam,
|
||||
// but plays nice with the Google formatted code generation.
|
||||
//
|
||||
// Try not to use required fields ever. Only do so if you are really really sure you'll never want them removed.
|
||||
// Optional should be preffered as it will make versioning easier and cleaner in the future if someone refactors
|
||||
// your message and wants to remove or rename fields.
|
||||
//
|
||||
// Use fixed64 for JobId_t, GID_t, or SteamID. This is appropriate for any field that is normally
|
||||
// going to be larger than 2^56. Otherwise use int64 for 64 bit values that are frequently smaller
|
||||
// than 2^56 as it will safe space on the wire in those cases.
|
||||
//
|
||||
// Similar to fixed64, use fixed32 for RTime32 or other 32 bit values that are frequently larger than
|
||||
// 2^28. It will safe space in those cases, otherwise use int32 which will safe space for smaller values.
|
||||
// An exception to this rule for RTime32 is if the value will frequently be zero rather than set to an actual
|
||||
// time.
|
||||
//
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
extend google.protobuf.FieldOptions {
|
||||
optional bool key_field = 60000 [ default = false ];
|
||||
}
|
||||
|
||||
|
||||
extend google.protobuf.MessageOptions{
|
||||
// Allows us to customize the pooling for different messages
|
||||
optional int32 msgpool_soft_limit = 60000 [default=32];
|
||||
optional int32 msgpool_hard_limit = 60001 [default=384];
|
||||
}
|
||||
|
||||
enum GCProtoBufMsgSrc
|
||||
{
|
||||
GCProtoBufMsgSrc_Unspecified = 0;
|
||||
GCProtoBufMsgSrc_FromSystem = 1;
|
||||
GCProtoBufMsgSrc_FromSteamID = 2;
|
||||
GCProtoBufMsgSrc_FromGC = 3;
|
||||
GCProtoBufMsgSrc_ReplySystem = 4;
|
||||
};
|
||||
|
||||
//
|
||||
// Message header, every protcol buffer based message starts with this.
|
||||
//
|
||||
message CMsgProtoBufHeader
|
||||
{
|
||||
option (msgpool_soft_limit) = 256;
|
||||
option (msgpool_hard_limit) = 1024;
|
||||
|
||||
// All fields here are optional.
|
||||
|
||||
// Client message header fields
|
||||
optional fixed64 client_steam_id = 1; // SteamID of the client sending this, typically set in all client originated messages.
|
||||
optional int32 client_session_id = 2; // SessionID of the client on the CM
|
||||
|
||||
// Source appId for inter-gc messages
|
||||
optional uint32 source_app_id = 3; // appId of source GC message sender
|
||||
|
||||
// Job routing (may be set on client or inter-server messages)
|
||||
optional fixed64 job_id_source = 10 [ default = 0xFFFFFFFFFFFFFFFF ]; // JobID that sent this message
|
||||
optional fixed64 job_id_target = 11 [ default = 0xFFFFFFFFFFFFFFFF ]; // The target job which is expected to be waiting on this message
|
||||
optional string target_job_name = 12; // the type of job to start when this message is received
|
||||
|
||||
optional int32 eresult = 13 [default = 2]; // For response jobs, the corresponding eresult
|
||||
optional string error_message = 14; // Optionally an error message in case of failure. Mostly used for debugging purpose.
|
||||
|
||||
// Where did this message originally enter the system? From a client, from another GC, etc
|
||||
optional GCProtoBufMsgSrc gc_msg_src = 200;
|
||||
// If this came from another GC, what is the GC that it came from
|
||||
optional uint32 gc_dir_index_source = 201;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Used to serialize CWebAPIKey objects.
|
||||
//
|
||||
message CMsgWebAPIKey
|
||||
{
|
||||
optional uint32 status = 1 [ default = 0xFF ];
|
||||
optional uint32 account_id = 2 [ default = 0 ];
|
||||
optional uint32 publisher_group_id = 3 [ default = 0 ];
|
||||
optional uint32 key_id = 4;
|
||||
optional string domain = 5;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// An HTTP request message
|
||||
//
|
||||
message CMsgHttpRequest
|
||||
{
|
||||
message RequestHeader
|
||||
{
|
||||
optional string name = 1;
|
||||
optional string value = 2;
|
||||
}
|
||||
|
||||
message QueryParam
|
||||
{
|
||||
optional string name = 1;
|
||||
optional bytes value = 2;
|
||||
}
|
||||
|
||||
optional uint32 request_method = 1;
|
||||
optional string hostname = 2;
|
||||
optional string url = 3;
|
||||
repeated RequestHeader headers = 4;
|
||||
repeated QueryParam get_params = 5;
|
||||
repeated QueryParam post_params = 6;
|
||||
optional bytes body = 7;
|
||||
optional uint32 absolute_timeout = 8;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// A web API request
|
||||
//
|
||||
message CMsgWebAPIRequest
|
||||
{
|
||||
optional string UNUSED_job_name = 1; // no longer used
|
||||
optional string interface_name = 2;
|
||||
optional string method_name = 3;
|
||||
optional uint32 version = 4;
|
||||
optional CMsgWebAPIKey api_key = 5;
|
||||
optional CMsgHttpRequest request = 6;
|
||||
optional uint32 routing_app_id = 7;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// An HTTP response
|
||||
//
|
||||
message CMsgHttpResponse
|
||||
{
|
||||
message ResponseHeader
|
||||
{
|
||||
optional string name = 1;
|
||||
optional string value = 2;
|
||||
}
|
||||
|
||||
optional uint32 status_code = 1;
|
||||
repeated ResponseHeader headers = 2;
|
||||
optional bytes body = 3;
|
||||
}
|
||||
|
||||
//
|
||||
// Message struct for k_EMsgAMFindAccounts
|
||||
//
|
||||
message CMsgAMFindAccounts
|
||||
{
|
||||
optional uint32 search_type = 1;
|
||||
optional string search_string = 2;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Message struct for k_EMsgAMFindAccountsResponse
|
||||
//
|
||||
message CMsgAMFindAccountsResponse
|
||||
{
|
||||
repeated fixed64 steam_id = 1;
|
||||
}
|
||||
|
||||
//
|
||||
// k_EMsgNotifyWatchdog
|
||||
//
|
||||
message CMsgNotifyWatchdog
|
||||
{
|
||||
optional uint32 source = 1; // Alert source
|
||||
optional uint32 alert_type = 2; // type of alert
|
||||
optional uint32 alert_destination = 3; // destination for alert
|
||||
optional bool critical = 4; // Is the alert critical
|
||||
optional uint32 time = 5; // world time that alert occurred
|
||||
optional uint32 appid = 6; // app to forward the alert to for alerts with alert_type set to AppID
|
||||
optional string text = 7; // Alert text
|
||||
}
|
||||
|
||||
//
|
||||
// k_EGCMsgGetLicenses
|
||||
//
|
||||
message CMsgAMGetLicenses
|
||||
{
|
||||
optional fixed64 steamid = 1; // the steam ID to fetch licenses for
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Used by CMsgAMGetLicensesResponse
|
||||
//
|
||||
message CMsgPackageLicense
|
||||
{
|
||||
optional uint32 package_id = 1; // ID of the package this license is for
|
||||
optional uint32 time_created = 2; // RTime32 when the license was granted
|
||||
optional uint32 owner_id = 3; // the original owner if this license. if this is different from given steamid, it's a borrowed package
|
||||
}
|
||||
|
||||
//
|
||||
// k_EMsgAMGetLicensesResponse
|
||||
//
|
||||
message CMsgAMGetLicensesResponse
|
||||
{
|
||||
repeated CMsgPackageLicense license = 1; // the list of licenses the user owns
|
||||
optional uint32 result = 2; // result code, k_EResultOK on success
|
||||
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// k_EMsgAMGetUserGameStats
|
||||
//
|
||||
message CMsgAMGetUserGameStats
|
||||
{
|
||||
optional fixed64 steam_id = 1; // ID of user
|
||||
optional fixed64 game_id = 2; // Game ID of stats to get
|
||||
repeated uint32 stats = 3;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// k_EMsgAMGetUserGameStatsResponse
|
||||
//
|
||||
message CMsgAMGetUserGameStatsResponse
|
||||
{
|
||||
optional fixed64 steam_id = 1; // ID of user
|
||||
optional fixed64 game_id = 2; // Game ID
|
||||
optional int32 eresult = 3 [default = 2]; // EResult with result of query. (Fields following are only valid if this is EResultOK.)
|
||||
|
||||
message Stats
|
||||
{
|
||||
optional uint32 stat_id = 1;
|
||||
optional uint32 stat_value = 2; // There are 4 of these, really only 8 bits each. Yay for compression!
|
||||
}
|
||||
|
||||
message Achievement_Blocks
|
||||
{
|
||||
optional uint32 achievement_id = 1;
|
||||
optional uint32 achievement_bit_id = 2;
|
||||
optional fixed32 unlock_time = 3; // There are only 32 of these, matching the achievment bitfields, we check on the receiver that
|
||||
}
|
||||
|
||||
repeated Stats stats = 4;
|
||||
|
||||
repeated Achievement_Blocks achievement_blocks = 5;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// k_EMsgAdminGCGetCommandList
|
||||
message CMsgGCGetCommandList
|
||||
{
|
||||
optional uint32 app_id = 1;
|
||||
optional string command_prefix = 2; // prefix of the command to filter by
|
||||
};
|
||||
|
||||
// k_EMsgAdminGCGetCommandListResponse
|
||||
message CMsgGCGetCommandListResponse
|
||||
{
|
||||
repeated string command_name = 1; // a list of command names
|
||||
};
|
||||
|
||||
//
|
||||
// k_EGCMsgMemCachedGet
|
||||
//
|
||||
message CGCMsgMemCachedGet
|
||||
{
|
||||
repeated string keys = 1;
|
||||
}
|
||||
|
||||
//
|
||||
// k_EGCMsgMemCachedGetResponse
|
||||
//
|
||||
message CGCMsgMemCachedGetResponse
|
||||
{
|
||||
message ValueTag
|
||||
{
|
||||
optional bool found = 1;
|
||||
optional bytes value = 2;
|
||||
}
|
||||
|
||||
repeated ValueTag values = 1;
|
||||
}
|
||||
|
||||
//
|
||||
// k_EGCMsgMemCachedSet
|
||||
//
|
||||
message CGCMsgMemCachedSet
|
||||
{
|
||||
message KeyPair
|
||||
{
|
||||
optional string name = 1;
|
||||
optional bytes value = 2;
|
||||
}
|
||||
|
||||
repeated KeyPair keys = 1;
|
||||
}
|
||||
|
||||
//
|
||||
// k_EGCMsgMemCachedDelete
|
||||
//
|
||||
message CGCMsgMemCachedDelete
|
||||
{
|
||||
repeated string keys = 1;
|
||||
}
|
||||
|
||||
//
|
||||
// k_EGCMsgMemCachedStats
|
||||
//
|
||||
message CGCMsgMemCachedStats
|
||||
{
|
||||
// Nothing, yet.
|
||||
}
|
||||
|
||||
//
|
||||
// k_EGCMsgMemCachedStatsResponse
|
||||
//
|
||||
message CGCMsgMemCachedStatsResponse
|
||||
{
|
||||
optional uint64 curr_connections = 1;
|
||||
optional uint64 cmd_get = 2;
|
||||
optional uint64 cmd_set = 3;
|
||||
optional uint64 cmd_flush = 4;
|
||||
optional uint64 get_hits = 5;
|
||||
optional uint64 get_misses = 6;
|
||||
optional uint64 delete_hits = 7;
|
||||
optional uint64 delete_misses = 8;
|
||||
optional uint64 bytes_read = 9;
|
||||
optional uint64 bytes_written = 10;
|
||||
optional uint64 limit_maxbytes = 11;
|
||||
optional uint64 curr_items = 12;
|
||||
optional uint64 evictions = 13;
|
||||
optional uint64 bytes = 14;
|
||||
}
|
||||
|
||||
//
|
||||
// k_EGCMsgSQLStats
|
||||
//
|
||||
message CGCMsgSQLStats
|
||||
{
|
||||
optional uint32 schema_catalog = 1;
|
||||
}
|
||||
|
||||
//
|
||||
// k_EGCMsgSQLStatsResponse
|
||||
//
|
||||
message CGCMsgSQLStatsResponse
|
||||
{
|
||||
optional uint32 threads = 1;
|
||||
optional uint32 threads_connected = 2;
|
||||
optional uint32 threads_active = 3;
|
||||
optional uint32 operations_submitted = 4;
|
||||
optional uint32 prepared_statements_executed = 5;
|
||||
optional uint32 non_prepared_statements_executed = 6;
|
||||
optional uint32 deadlock_retries = 7;
|
||||
optional uint32 operations_timed_out_in_queue = 8;
|
||||
optional uint32 errors = 9;
|
||||
}
|
||||
|
||||
// k_EMsgAMAddFreeLicense
|
||||
message CMsgAMAddFreeLicense
|
||||
{
|
||||
optional fixed64 steamid = 1; // SteamID of account
|
||||
optional uint32 ip_public = 2; // IP of client (zero if not a client-initiated message)
|
||||
optional uint32 packageid = 3; // ID for package to purchase. Should be k_uPackageIdInvalid if shopping cart gid set
|
||||
optional string store_country_code = 4; // country code to use for purchase
|
||||
};
|
||||
|
||||
// k_EMsgAMAddFreeLicenseResponse
|
||||
message CMsgAMAddFreeLicenseResponse
|
||||
{
|
||||
optional int32 eresult = 1 [default = 2]; // EResult with result of Purchase.
|
||||
optional int32 purchase_result_detail = 2; // Detailed result information
|
||||
optional fixed64 transid = 3; // ID of the created transaction
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// k_EGCMsgGetIPLocation
|
||||
//
|
||||
message CGCMsgGetIPLocation
|
||||
{
|
||||
repeated fixed32 ips = 1;
|
||||
}
|
||||
|
||||
//
|
||||
// k_EGCMsgGetIPLocationResponse
|
||||
//
|
||||
message CIPLocationInfo
|
||||
{
|
||||
optional uint32 ip = 1;
|
||||
optional float latitude = 2;
|
||||
optional float longitude = 3;
|
||||
optional string country = 4;
|
||||
optional string state = 5;
|
||||
optional string city = 6;
|
||||
}
|
||||
|
||||
message CGCMsgGetIPLocationResponse
|
||||
{
|
||||
repeated CIPLocationInfo infos = 1;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// k_EGCMsgSystemStatsSchema
|
||||
//
|
||||
message CGCMsgSystemStatsSchema
|
||||
{
|
||||
optional uint32 gc_app_id = 1;
|
||||
optional bytes schema_kv = 2;
|
||||
}
|
||||
|
||||
//
|
||||
// k_EGCMsgGetSystemStats
|
||||
//
|
||||
message CGCMsgGetSystemStats
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// k_EGCMsgGetSystemStatsResponse
|
||||
//
|
||||
message CGCMsgGetSystemStatsResponse
|
||||
{
|
||||
optional uint32 gc_app_id = 1;
|
||||
optional bytes stats_kv = 2;
|
||||
// statically included in GCHost's stats
|
||||
optional uint32 active_jobs = 3;
|
||||
optional uint32 yielding_jobs = 4;
|
||||
optional uint32 user_sessions = 5;
|
||||
optional uint32 game_server_sessions = 6;
|
||||
optional uint32 socaches = 7;
|
||||
optional uint32 socaches_to_unload = 8;
|
||||
optional uint32 socaches_loading = 9;
|
||||
optional uint32 writeback_queue = 10;
|
||||
optional uint32 steamid_locks = 11;
|
||||
optional uint32 logon_queue = 12;
|
||||
optional uint32 logon_jobs = 13;
|
||||
}
|
||||
|
||||
// k_EGCMsgSendEmail
|
||||
message CMsgAMSendEmail
|
||||
{
|
||||
message ReplacementToken
|
||||
{
|
||||
optional string token_name = 1;
|
||||
optional string token_value = 2;
|
||||
}
|
||||
message PersonaNameReplacementToken
|
||||
{
|
||||
optional fixed64 steamid = 1;
|
||||
optional string token_name = 2;
|
||||
}
|
||||
optional fixed64 steamid = 1;
|
||||
optional uint32 email_msg_type = 2;
|
||||
optional uint32 email_format = 3;
|
||||
repeated PersonaNameReplacementToken persona_name_tokens = 5;
|
||||
|
||||
optional uint32 source_gc = 6;
|
||||
repeated ReplacementToken tokens = 7;
|
||||
}
|
||||
|
||||
// k_EGCMsgSendEmailResponse
|
||||
message CMsgAMSendEmailResponse
|
||||
{
|
||||
optional uint32 eresult = 1 [default = 2];
|
||||
}
|
||||
|
||||
// k_EGCMsgGetEmailTemplate
|
||||
message CMsgGCGetEmailTemplate
|
||||
{
|
||||
optional uint32 app_id = 1;
|
||||
optional uint32 email_msg_type = 2;
|
||||
optional int32 email_lang = 3;
|
||||
optional int32 email_format = 4;
|
||||
}
|
||||
|
||||
// k_EGCMsgGetEmailTemplateResponse
|
||||
message CMsgGCGetEmailTemplateResponse
|
||||
{
|
||||
optional uint32 eresult = 1 [default = 2];
|
||||
optional bool template_exists = 2;
|
||||
optional string template = 3;
|
||||
}
|
||||
|
||||
// k_EMsgAMGrantGuestPasses2
|
||||
message CMsgAMGrantGuestPasses2
|
||||
{
|
||||
optional fixed64 steam_id = 1;
|
||||
optional uint32 package_id = 2;
|
||||
optional int32 passes_to_grant = 3;
|
||||
optional int32 days_to_expiration = 4;
|
||||
optional int32 action = 5;
|
||||
}
|
||||
|
||||
// k_EMsgAMGrantGuestPasses2Response
|
||||
message CMsgAMGrantGuestPasses2Response
|
||||
{
|
||||
optional int32 eresult = 1 [default = 2];
|
||||
optional int32 passes_granted = 2 [default = 0];
|
||||
}
|
||||
|
||||
// k_EGCMsgGetAccountDetails
|
||||
message CGCSystemMsg_GetAccountDetails
|
||||
{
|
||||
option (msgpool_soft_limit) = 128;
|
||||
option (msgpool_hard_limit) = 512;
|
||||
|
||||
optional fixed64 steamid = 1; // User to get details for
|
||||
optional uint32 appid = 2; // appid of the source GC
|
||||
}
|
||||
|
||||
message CGCSystemMsg_GetAccountDetails_Response
|
||||
{
|
||||
option (msgpool_soft_limit) = 128;
|
||||
option (msgpool_hard_limit) = 512;
|
||||
|
||||
optional uint32 eresult_deprecated = 1 [ default = 2 ]; // Result of the request
|
||||
optional string account_name = 2; // Login name for the user
|
||||
optional string persona_name = 3; // Diplay name for the user
|
||||
optional bool is_profile_public = 4; // Is the user's profile public
|
||||
optional bool is_inventory_public = 5; // Is the user's inventory public
|
||||
//optional bool is_trusted = 6; // Is the user trusted
|
||||
optional bool is_vac_banned = 7; // Is the user vac banned
|
||||
optional bool is_cyber_cafe = 8; // Is the user a cybe cafe
|
||||
optional bool is_school_account = 9; // Is the user a school account
|
||||
optional bool is_limited = 10; // Is the user limited
|
||||
optional bool is_subscribed = 11; // Is the user subscribed to this app
|
||||
optional uint32 package = 12; // The package the user owns the app through
|
||||
optional bool is_free_trial_account = 13; // Is the user playing the game for free
|
||||
optional uint32 free_trial_expiration = 14; // If the user is playing for free, when does it expire?
|
||||
optional bool is_low_violence = 15; // Is the user restricted to low-violence for this app
|
||||
optional bool is_account_locked_down = 16; // Is the user's account locked
|
||||
optional bool is_community_banned = 17; // Is the user banned from performing community actions
|
||||
optional bool is_trade_banned = 18; // Is the user banned from trading items to other users on Steam
|
||||
optional uint32 trade_ban_expiration = 19; // The time at which the user is unbanned from trading
|
||||
optional uint32 accountid = 20; // The account ID of the user we're responding about
|
||||
optional uint32 suspension_end_time = 21; // If suspended (ban that ends), the date/time the suspension ends
|
||||
optional string currency = 22; // The currency associated with this account
|
||||
optional uint32 steam_level = 23; // The Steam level of the user
|
||||
optional uint32 friend_count = 24; // Number of friends
|
||||
optional uint32 account_creation_time = 25; // Time when the account was created
|
||||
optional bool is_steamguard_enabled = 27; // Is SteamGuard enabled
|
||||
optional bool is_phone_verified = 28; // Has a verified phone number
|
||||
optional bool is_two_factor_auth_enabled = 29; // Has SteamGuard two factor auth
|
||||
optional uint32 two_factor_enabled_time = 30; // Time two factor was added
|
||||
optional uint32 phone_verification_time = 31; // Time phone was verified
|
||||
optional uint64 phone_id = 33; // Phone identifier
|
||||
optional bool is_phone_identifying = 34; // Phone is useful for identity
|
||||
}
|
||||
|
||||
//
|
||||
// k_EGCMsgCheckClanMembership
|
||||
//
|
||||
message CMsgGCCheckClanMembership
|
||||
{
|
||||
optional fixed64 steamid = 1; // [ (description) = "User whose group memberships we want" ];
|
||||
optional uint32 clanid = 2; // [ (description) = "Clan to check membership against" ];
|
||||
|
||||
}
|
||||
|
||||
message CMsgGCCheckClanMembership_Response
|
||||
{
|
||||
optional bool ismember = 1; // [ (description) = "Whether the user in question is a member of the group in question" ];
|
||||
}
|
||||
|
||||
//
|
||||
// k_EGCMsgGetPersonaNames
|
||||
//
|
||||
message CMsgGCGetPersonaNames
|
||||
{
|
||||
repeated fixed64 steamids = 1; // Users whose persona names we want
|
||||
}
|
||||
|
||||
message CMsgGCGetPersonaNames_Response
|
||||
{
|
||||
message PersonaName
|
||||
{
|
||||
optional fixed64 steamid = 1; // User we could get a name for
|
||||
optional string persona_name = 2; // Display name for that user
|
||||
}
|
||||
|
||||
repeated PersonaName succeeded_lookups = 1; // Users we could get names for
|
||||
repeated fixed64 failed_lookup_steamids = 2; // Users we failed to get a names for
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// k_EGCMsgCheckFriendship
|
||||
//
|
||||
message CMsgGCCheckFriendship
|
||||
{
|
||||
optional fixed64 steamid_left = 1; // User whose friends list we'll load
|
||||
optional fixed64 steamid_right = 2; // User to look for in the list we load
|
||||
}
|
||||
|
||||
message CMsgGCCheckFriendship_Response
|
||||
{
|
||||
optional bool success = 1; // Whether the API calls all succeeded
|
||||
optional bool found_friendship = 2; // Denotes whether the users are friends (false on API failure)
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// k_EGCMsgMasterSetDirectory
|
||||
//
|
||||
message CMsgGCMsgMasterSetDirectory
|
||||
{
|
||||
message SubGC
|
||||
{
|
||||
optional uint32 dir_index = 1; // The index in the GC directory indicating what role this GC serves
|
||||
optional string name = 2; // A string to give the GC a name for asserts/logs/connection, etc
|
||||
optional string box = 3; // The box that this GC is expected to be associated with
|
||||
optional string command_line = 4; // Additional command line parameters to provide for this GC instance
|
||||
optional string gc_binary = 5; // The binary that should be launched for this GC. This can be left blank to launch the default binary
|
||||
}
|
||||
|
||||
optional uint32 master_dir_index = 1; // The index of the master GC so that it knows how to setup the routing tables to include the master and sub GCs
|
||||
repeated SubGC dir = 2; // A listing of the various sub GCs that the GCH should create
|
||||
}
|
||||
|
||||
message CMsgGCMsgMasterSetDirectory_Response
|
||||
{
|
||||
optional int32 eresult = 1 [default = 2]; // Could the GC start the processes? It doesn't mean they will all get initialized and each sub GC still needs to ack, but catches failure earlier
|
||||
}
|
||||
|
||||
//
|
||||
// k_EGCMsgWebAPIJobRequestForwardResponse
|
||||
//
|
||||
message CMsgGCMsgWebAPIJobRequestForwardResponse
|
||||
{
|
||||
optional uint32 dir_index = 1; // The directory index of the GC which has been delegated to handle this particular WebAPI request
|
||||
}
|
||||
|
||||
//
|
||||
// k_EGCMsgGetPurchaseTrustStatus
|
||||
//
|
||||
message CGCSystemMsg_GetPurchaseTrust_Request
|
||||
{
|
||||
optional fixed64 steamid = 1; // User to get details for
|
||||
}
|
||||
|
||||
message CGCSystemMsg_GetPurchaseTrust_Response
|
||||
{
|
||||
optional bool has_prior_purchase_history = 1; // The user has prior purchase history with no recent gaps in purchase activity
|
||||
optional bool has_no_recent_password_resets = 2; // The user hasn't had their password reset recently
|
||||
optional bool is_wallet_cash_trusted = 3; // False if the user has recently used a new payment method to fund his or her wallet
|
||||
optional uint32 time_all_trusted = 4; // The time that the user will be trusted in all of the given fields if he or she were to complete a microtransaction right now
|
||||
}
|
||||
|
||||
// k_EMsgGCHAccountVacStatusChange
|
||||
message CMsgGCHAccountVacStatusChange
|
||||
{
|
||||
optional fixed64 steam_id = 1;
|
||||
optional uint32 app_id = 2;
|
||||
optional uint32 rtime_vacban_starts = 3;
|
||||
optional bool is_banned_now = 4;
|
||||
optional bool is_banned_future = 5;
|
||||
}
|
||||
|
||||
// k_EMsgGCHAccountTradeBanStatusChange
|
||||
message CMsgGCHAccountTradeBanStatusChange
|
||||
{
|
||||
optional fixed64 steamid = 1;
|
||||
optional uint32 appid = 2;
|
||||
optional bool is_banned = 3;
|
||||
optional uint32 time_banned_until = 4;
|
||||
}
|
||||
|
||||
// k_EMsgGCHAccountLockStatusChange
|
||||
message CMsgGCHAccountLockStatusChange
|
||||
{
|
||||
optional fixed64 steamid = 1;
|
||||
optional uint32 appid = 2;
|
||||
optional bool is_locked = 3;
|
||||
}
|
||||
|
||||
// k_EMsgGCHVacVerificationChange
|
||||
message CMsgGCHVacVerificationChange
|
||||
{
|
||||
optional fixed64 steamid = 1;
|
||||
optional uint32 appid = 2;
|
||||
optional bool is_verified = 3;
|
||||
}
|
||||
|
||||
// k_EMsgGCHAccountPhoneNumberChange
|
||||
message CMsgGCHAccountPhoneNumberChange
|
||||
{
|
||||
optional fixed64 steamid = 1;
|
||||
optional uint32 appid = 2;
|
||||
optional uint64 phone_id = 3;
|
||||
optional bool is_verified = 4;
|
||||
optional bool is_identifying = 5;
|
||||
}
|
||||
|
||||
// k_EMsgGCHAccountTwoFactorChange
|
||||
message CMsgGCHAccountTwoFactorChange
|
||||
{
|
||||
optional fixed64 steamid = 1;
|
||||
optional uint32 appid = 2;
|
||||
optional bool twofactor_enabled = 3;
|
||||
}
|
||||
|
||||
//
|
||||
// k_EGCMsgGetPartnerAccountLink
|
||||
//
|
||||
message CMsgGCGetPartnerAccountLink
|
||||
{
|
||||
optional fixed64 steamid = 1; // User whose partner account link details we want to get
|
||||
}
|
||||
|
||||
message CMsgGCGetPartnerAccountLink_Response
|
||||
{
|
||||
optional uint32 pwid = 1; // Perfect World ID (not specified if not linked)
|
||||
optional uint32 nexonid = 2; // Nexon ID (not specified if not linked)
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// k_EGCMsgMasterSetWebAPIRouting and k_EGCMsgMasterSetClientMsgRouting
|
||||
//
|
||||
message CMsgGCRoutingInfo
|
||||
{
|
||||
enum RoutingMethod
|
||||
{
|
||||
RANDOM = 0; // random instead of round-robin so that we don't need to track state per routing pool
|
||||
DISCARD = 1;
|
||||
CLIENT_STEAMID = 2;
|
||||
PROTOBUF_FIELD_UINT64 = 3;
|
||||
WEBAPI_PARAM_UINT64 = 4;
|
||||
}
|
||||
|
||||
repeated uint32 dir_index = 1; // One or more directory indices which are potential targets for this route
|
||||
optional RoutingMethod method = 2 [ default = RANDOM ]; // Method by which the route choses its target from multiple dir_index values
|
||||
optional RoutingMethod fallback = 3 [ default = DISCARD ]; // Fallback method to use when default method is not applicable (eg, field can't be parsed)
|
||||
optional uint32 protobuf_field = 4; // For PROTOBUF_FIELD_UINT64, the protobuf field number to decode as a uint64 for routing
|
||||
optional string webapi_param = 5; // For WEBAPI_PARAM_UINT64 method, the case-insensitive name of the webapi parameter
|
||||
}
|
||||
|
||||
message CMsgGCMsgMasterSetWebAPIRouting
|
||||
{
|
||||
message Entry
|
||||
{
|
||||
optional string interface_name = 1;
|
||||
optional string method_name = 2;
|
||||
optional CMsgGCRoutingInfo routing = 3;
|
||||
}
|
||||
repeated Entry entries = 1;
|
||||
}
|
||||
|
||||
message CMsgGCMsgMasterSetClientMsgRouting
|
||||
{
|
||||
message Entry
|
||||
{
|
||||
optional uint32 msg_type = 1; // Client message ID to be routed; top bit is ignored for historical reasons
|
||||
optional CMsgGCRoutingInfo routing = 2;
|
||||
}
|
||||
repeated Entry entries = 1;
|
||||
}
|
||||
|
||||
message CMsgGCMsgMasterSetWebAPIRouting_Response
|
||||
{
|
||||
optional int32 eresult = 1 [ default = 2 ]; // Success or failure code from the GCH when processing k_EGCMsgMasterSetWebAPIRouting
|
||||
}
|
||||
|
||||
message CMsgGCMsgMasterSetClientMsgRouting_Response
|
||||
{
|
||||
optional int32 eresult = 1 [ default = 2 ]; // Success or failure code from the GCH when processing k_EGCMsgMasterSetClientMsgRouting
|
||||
}
|
||||
|
||||
|
||||
// k_EGCMsgSetOptions
|
||||
message CMsgGCMsgSetOptions
|
||||
{
|
||||
enum Option
|
||||
{
|
||||
// Notifications (aka "data streams" - unsoliticed messages from Steam) - default disabled, specify to opt-in
|
||||
NOTIFY_USER_SESSIONS = 0;
|
||||
NOTIFY_SERVER_SESSIONS = 1;
|
||||
NOTIFY_ACHIEVEMENTS = 2;
|
||||
NOTIFY_VAC_ACTION = 3;
|
||||
|
||||
// todo: other options? should start options higher up, like 20+, to save room for streams?
|
||||
}
|
||||
repeated Option options = 1;
|
||||
|
||||
|
||||
// The client_msg_ranges field indicates which client messages, if any, this GC should receive copies of
|
||||
message MessageRange
|
||||
{
|
||||
required uint32 low = 1;
|
||||
required uint32 high = 2;
|
||||
}
|
||||
repeated MessageRange client_msg_ranges = 2;
|
||||
|
||||
|
||||
// The GCSQL version field is only read by the GC.EXE host to determine support for enums, etc; the GCH doesn't care
|
||||
enum GCSQLVersion
|
||||
{
|
||||
GCSQL_VERSION_BASELINE = 1; // baseline
|
||||
GCSQL_VERSION_BOOLTYPE = 2; // added explicit support for bool types (instead of int8)
|
||||
}
|
||||
optional GCSQLVersion gcsql_version = 3;
|
||||
}
|
||||
|
||||
|
||||
// k_EMsgGCHUpdateSession
|
||||
message CMsgGCHUpdateSession
|
||||
{
|
||||
optional fixed64 steam_id = 1;
|
||||
optional uint32 app_id = 2;
|
||||
optional bool online = 3;
|
||||
optional fixed64 server_steam_id = 4; // For a game client, the steam_id of the current server
|
||||
optional uint32 server_addr = 5; // The IP address of the current server (or self for server state)
|
||||
optional uint32 server_port = 6; // The IP port of the server (or self for server state)
|
||||
optional uint32 os_type = 7;
|
||||
optional uint32 client_addr = 8; // For a game client, the public IP address of the client
|
||||
|
||||
message ExtraField
|
||||
{
|
||||
optional string name = 1;
|
||||
optional string value = 2;
|
||||
}
|
||||
repeated ExtraField extra_fields = 9;
|
||||
}
|
||||
|
||||
//
|
||||
// k_EGCMsgVSReportedSuspiciousActivity
|
||||
//
|
||||
message CMsgNotificationOfSuspiciousActivity
|
||||
{
|
||||
optional fixed64 steamid = 1;
|
||||
optional uint32 appid = 2;
|
||||
|
||||
message MultipleGameInstances
|
||||
{
|
||||
optional uint32 app_instance_count = 1;
|
||||
repeated fixed64 other_steamids = 2;
|
||||
}
|
||||
optional MultipleGameInstances multiple_instances = 3;
|
||||
}
|
||||
|
||||
// Do not remove this comment due to a bug on the Mac OS X protobuf compiler
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
$MacroRequired GENERATED_PROTO_DIR
|
||||
|
||||
$Project
|
||||
{
|
||||
$Folder "Protobuf Files"
|
||||
{
|
||||
$File "$SRCDIR\gcsdk\steammessages.proto"
|
||||
$Folder "Generated Files"
|
||||
{
|
||||
$DynamicFile "$GENERATED_PROTO_DIR\steammessages.pb.h"
|
||||
$DynamicFile "$GENERATED_PROTO_DIR\steammessages.pb.cc" [!$OSXALL]
|
||||
{
|
||||
$Configuration
|
||||
{
|
||||
$Compiler
|
||||
{
|
||||
$Create/UsePrecompiledHeader "Not Using Precompiled Headers"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,751 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "tslist.h"
|
||||
#include <workthreadpool.h>
|
||||
#include <gclogger.h>
|
||||
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
namespace GCSDK {
|
||||
|
||||
IWorkThreadPoolSignal *CWorkThreadPool::sm_pWorkItemsCompletedSignal = NULL;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: CWorkThread constructors
|
||||
//-----------------------------------------------------------------------------
|
||||
CWorkThread::CWorkThread( CWorkThreadPool *pThreadPool )
|
||||
: m_pThreadPool( pThreadPool ),
|
||||
m_bExitThread( false ),
|
||||
m_bFinished( false )
|
||||
{
|
||||
}
|
||||
|
||||
CWorkThread::CWorkThread( CWorkThreadPool *pThreadPool, const char *pszName )
|
||||
: m_pThreadPool( pThreadPool ),
|
||||
m_bExitThread( false ),
|
||||
m_bFinished( false )
|
||||
{
|
||||
SetName( pszName );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Tell work thread pool not to set event on every item added (SetEvent is very expensive)
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWorkThreadPool::SetNeverSetEventOnAdd( bool bNeverSet )
|
||||
{
|
||||
bool bWasSet = m_bNeverSetOnAdd;
|
||||
m_bNeverSetOnAdd = bNeverSet;
|
||||
|
||||
// In case of disabling set right away to make sure if we have pending work we execute it now with no latency
|
||||
if ( bWasSet && !m_bNeverSetOnAdd )
|
||||
m_EventNewWorkItem.Set();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: performs the work loop for the thread, waits for work,
|
||||
// notifies the owner (the pool) as it completes work and before it exits
|
||||
//-----------------------------------------------------------------------------
|
||||
int CWorkThread::Run()
|
||||
{
|
||||
// manage our thread pool's statistics
|
||||
++m_pThreadPool->m_cThreadsRunning;
|
||||
|
||||
#ifdef _SERVER
|
||||
g_CompletionPortManager.AssociateCallingThreadWithIOCP();
|
||||
#endif
|
||||
|
||||
OnStart();
|
||||
|
||||
#if 0 // need to port over new vprof code
|
||||
#if defined( VPROF_ENABLED )
|
||||
CVProfile *pProfile = GetVProfProfileForCurrentThread();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
CWorkThreadPool *pPool = m_pThreadPool;
|
||||
|
||||
int nIterations = 0;
|
||||
const int nMaxFastIterations = 4;
|
||||
while ( !m_bExitThread )
|
||||
{
|
||||
#if 0 // game vprof doesn't yet support TLS'd vprof instances, until new vprof code is ported
|
||||
#if defined( VPROF_ENABLED )
|
||||
if ( pProfile )
|
||||
pProfile->MarkFrame( GetName() );
|
||||
#endif
|
||||
#endif
|
||||
pPool->m_cActiveThreads++;
|
||||
|
||||
nIterations = 0;
|
||||
while ( (pPool->BNeverSetEventOnAdd() && nIterations < nMaxFastIterations) || nIterations == 0 )
|
||||
{
|
||||
// process any items which have arrived
|
||||
CWorkItem *pWorkItem = pPool->GetNextWorkItemToProcess( );
|
||||
while ( pWorkItem )
|
||||
{
|
||||
#if 0
|
||||
pPool->m_StatWaitTime.Update( pWorkItem->WaitingTime() );
|
||||
#endif
|
||||
if ( pWorkItem->HasTimedOut() )
|
||||
{
|
||||
pWorkItem->m_bCanceled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// call the work item to do its work
|
||||
pWorkItem->m_bCanceled = false;
|
||||
|
||||
CFastTimer fastTimer;
|
||||
fastTimer.Start();
|
||||
pWorkItem->m_bRunning = true;
|
||||
bool bSuccess = pWorkItem->ThreadProcess( this );
|
||||
pWorkItem->m_bRunning = false;
|
||||
fastTimer.End();
|
||||
CCycleCount cycleCount = fastTimer.GetDuration();
|
||||
pWorkItem->SetCycleCount(cycleCount);
|
||||
#if 0
|
||||
pPool->m_StatExecutionTime.Update( cycleCount.GetUlMicroseconds() );
|
||||
#endif
|
||||
if ( bSuccess )
|
||||
pPool->m_cSuccesses ++;
|
||||
else
|
||||
pWorkItem->m_bResubmit ? pPool->m_cRetries++ : pPool->m_cFailures++;
|
||||
}
|
||||
|
||||
// do we need to resubmit this item?
|
||||
if ( pWorkItem->m_bResubmit )
|
||||
{
|
||||
pWorkItem->m_bResubmit = false;
|
||||
pWorkItem->m_bCanceled = false;
|
||||
// put it at the tail of the incoming queue
|
||||
pPool->AddWorkItem( pWorkItem );
|
||||
pWorkItem->Release(); // dec since AddWorkItem added 1 more again
|
||||
}
|
||||
else
|
||||
{
|
||||
// put it in the outgoing queue
|
||||
pPool->OnWorkItemCompleted( pWorkItem );
|
||||
}
|
||||
|
||||
// If we are flagged as exiting don't try to get more work, we need to exit right away and orphan the work
|
||||
// to avoid blocking shutdown.
|
||||
if ( !m_bExitThread )
|
||||
{
|
||||
// get the next work item (if any)
|
||||
pWorkItem = pPool->GetNextWorkItemToProcess( );
|
||||
}
|
||||
else
|
||||
{
|
||||
pWorkItem = NULL;
|
||||
}
|
||||
|
||||
#if 0 // game vprof doesn't yet support TLS'd vprof instances, until new vprof code is ported
|
||||
#if defined( VPROF_ENABLED )
|
||||
if ( pProfile && pWorkItem )
|
||||
pProfile->MarkFrame( GetName() );
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
if ( m_bExitThread )
|
||||
break;
|
||||
|
||||
++nIterations;
|
||||
if ( pPool->BNeverSetEventOnAdd() && nIterations < nMaxFastIterations )
|
||||
{
|
||||
VPROF_BUDGET( "CWorkThread -- Sleep", VPROF_BUDGETGROUP_SLEEPING );
|
||||
ThreadSleep( 2 );
|
||||
}
|
||||
}
|
||||
|
||||
pPool->m_cActiveThreads--;
|
||||
|
||||
// wait for a new work item to arrive in the queue, check the counts first just to be sure
|
||||
{
|
||||
VPROF_BUDGET( "CWorkThread -- Sleep", VPROF_BUDGETGROUP_SLEEPING );
|
||||
#ifdef _SERVER
|
||||
if ( pPool->BNeverSetEventOnAdd() )
|
||||
pPool->m_EventNewWorkItem.Wait( 15 );
|
||||
else
|
||||
pPool->m_EventNewWorkItem.Wait( 50 );
|
||||
#else
|
||||
pPool->m_EventNewWorkItem.Wait( 50 );
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Since we are exiting, we must have been signaled to shutdown, and we should signal any remaining threads
|
||||
// since each signal wakes only one thread.
|
||||
pPool->m_EventNewWorkItem.Set();
|
||||
|
||||
m_bFinished = true;
|
||||
|
||||
// updates stats
|
||||
--m_pThreadPool->m_cThreadsRunning;
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Construct a new CWorkThreadPool object
|
||||
//-----------------------------------------------------------------------------
|
||||
CWorkThreadPool::CWorkThreadPool( const char *pszThreadName )
|
||||
:
|
||||
#if 0
|
||||
m_StatWaitTime( 100 ),
|
||||
m_StatExecutionTime( 100 ),
|
||||
#endif
|
||||
m_bThreadsInitialized( false ),
|
||||
m_cThreadsRunning( 0 ),
|
||||
m_cActiveThreads( 0 ),
|
||||
m_bMayHaveJobTimeouts( false ),
|
||||
m_bExiting( false ),
|
||||
m_bAutoCreateThreads( false ),
|
||||
m_cMaxThreads( 0 ),
|
||||
m_cFailures( 0 ),
|
||||
m_cSuccesses( 0 ),
|
||||
m_pWorkThreadConstructor( NULL ),
|
||||
m_ulLastCompletedSequenceNumber( 0 ),
|
||||
m_ulLastUsedSequenceNumber( 0 ),
|
||||
m_ulLastDispatchedSequenceNumber( 0 ),
|
||||
m_bEnsureOutputOrdering( false ),
|
||||
m_bNeverSetOnAdd( false )
|
||||
{
|
||||
Assert( pszThreadName != NULL );
|
||||
Q_strncpy( m_szThreadNamePfx, pszThreadName, sizeof( m_szThreadNamePfx ) );
|
||||
m_LimitTimerCreateNewThreads.SetLimit( 1 );
|
||||
|
||||
m_pTSQueueToProcess = new CTSQueue< CWorkItem* >;
|
||||
m_pTSQueueCompleted = new CTSQueue< CWorkItem* >;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: destructor; does assertion checks to make sure we weere shut down cleanly
|
||||
// cleans up even if we weren't cleanly stopped
|
||||
//-----------------------------------------------------------------------------
|
||||
CWorkThreadPool::~CWorkThreadPool()
|
||||
{
|
||||
// If you hit this you probably didn't call StopWorkThreads() first
|
||||
AssertMsg1( ( !m_bThreadsInitialized || m_bExiting ) && 0 == m_cThreadsRunning,
|
||||
"CWorkThreadPool::~CWorkThreadPool(): Thread pool %s shutdown incorrectly.\n",
|
||||
m_szThreadNamePfx );
|
||||
|
||||
if ( m_WorkThreads.Count() )
|
||||
{
|
||||
StopWorkThreads();
|
||||
Assert( 0 == m_WorkThreads.Count() );
|
||||
}
|
||||
|
||||
Assert( 0 == m_cThreadsRunning );
|
||||
|
||||
// WARNING: We need to release any items left in the queues
|
||||
CWorkItem *pWorkItem = NULL;
|
||||
if ( m_pTSQueueCompleted->Count() > 0 )
|
||||
{
|
||||
EmitWarning( SPEW_THREADS, 2, "CWorkThreadPool::~CWorkThreadPool: work complete queue not empty, %d items discarded.\n", m_pTSQueueCompleted->Count() );
|
||||
pWorkItem = NULL;
|
||||
while ( m_pTSQueueCompleted->PopItem( &pWorkItem ) )
|
||||
{
|
||||
while( pWorkItem->Release() )
|
||||
{
|
||||
/* nothing */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( m_pTSQueueToProcess->Count() > 0 )
|
||||
{
|
||||
EmitWarning( SPEW_THREADS, 2, "CWorkThreadPool::~CWorkThreadPool: work processing queue not empty: %d items discarded.\n", m_pTSQueueToProcess->Count() );
|
||||
while ( m_pTSQueueToProcess->PopItem( &pWorkItem ) )
|
||||
{
|
||||
while( pWorkItem->Release() )
|
||||
{
|
||||
/* nothing */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete m_pTSQueueToProcess;
|
||||
delete m_pTSQueueCompleted;
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: estimate the current backlog time using previous execution time,
|
||||
// the number of outstanding items, and the number of running threads
|
||||
//-----------------------------------------------------------------------------
|
||||
uint64 CWorkThreadPool::GetCurrentBacklogTime() const
|
||||
{
|
||||
if ( m_WorkThreads.Count() == 0 )
|
||||
return 0;
|
||||
return ( m_pTSQueueToProcess->Count() * m_StatExecutionTime.GetUlAvg() ) / m_WorkThreads.Count();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
int CWorkThreadPool::AddWorkThread( CWorkThread *pThread )
|
||||
{
|
||||
AUTO_LOCK( m_WorkThreadMutex );
|
||||
Assert( pThread );
|
||||
return m_WorkThreads.AddToTail( pThread );
|
||||
}
|
||||
|
||||
|
||||
void CWorkThreadPool::StartWorkThread( CWorkThread *pWorkThread, int iName )
|
||||
{
|
||||
char rgchThreadName[32];
|
||||
Q_snprintf( rgchThreadName, sizeof( rgchThreadName ), "%s:%d", m_szThreadNamePfx, iName );
|
||||
pWorkThread->SetName( rgchThreadName );
|
||||
if ( !pWorkThread->Start() )
|
||||
EmitError( SPEW_THREADS, "CWorkThreadPool::StartWorkThread: Thread creation failed.\n" );
|
||||
}
|
||||
|
||||
|
||||
void CWorkThreadPool::StartWorkThreads()
|
||||
{
|
||||
m_bThreadsInitialized = true;
|
||||
if ( 0 == m_WorkThreads.Count() )
|
||||
{
|
||||
EmitWarning( SPEW_THREADS, 2, "CWorkThreadPool::StartWorkThreads: called with no threads in the pool, this is probably a bug.\n" );
|
||||
return;
|
||||
}
|
||||
m_bExiting = false;
|
||||
m_cThreadsRunning = 0;
|
||||
AUTO_LOCK( m_WorkThreadMutex );
|
||||
FOR_EACH_VEC( m_WorkThreads, i )
|
||||
{
|
||||
StartWorkThread( m_WorkThreads[i], i );
|
||||
}
|
||||
|
||||
// XXX why?
|
||||
while ( m_cThreadsRunning == (uint) 0 )
|
||||
{
|
||||
ThreadSleep( 1 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: stops whatever work threads we're running
|
||||
// this must be called before the thread pool object is destroyed
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWorkThreadPool::StopWorkThreads()
|
||||
{
|
||||
// indicate that we're shutting down;
|
||||
// don't accept more work in this thread
|
||||
m_bExiting = true;
|
||||
|
||||
AUTO_LOCK( m_WorkThreadMutex );
|
||||
|
||||
FOR_EACH_VEC( m_WorkThreads, i )
|
||||
{
|
||||
m_WorkThreads[i]->m_bExitThread = true;
|
||||
m_WorkThreads[i]->Cancel();
|
||||
}
|
||||
|
||||
// loop until all threads are dead
|
||||
while ( true )
|
||||
{
|
||||
// This thread already holds the mutex; recursive try-lock should always succeed
|
||||
DbgVerify( BTryDeleteExitedWorkerThreads() );
|
||||
|
||||
if ( m_WorkThreads.Count() == 0 )
|
||||
break;
|
||||
|
||||
// Keep waking up threads until they're all dead.
|
||||
m_EventNewWorkItem.Set();
|
||||
|
||||
#ifdef _PS3
|
||||
// call to abort any running call to gethostbyname().
|
||||
// this is called over all the remaining work threads, while
|
||||
// waiting for the rest of the work threads to finish so that they won't
|
||||
// spuriously block on new calls to gethostbyname() as the
|
||||
// sys_net_abort_resolver call only stops the next call to the
|
||||
// network API, not any future calls.
|
||||
|
||||
FOR_EACH_VEC( m_WorkThreads, iPS3 )
|
||||
{
|
||||
// PS3 hack to abort gethostbyname() calls that may be blocking...
|
||||
sys_net_abort_resolver( m_WorkThreads[ iPS3 ]->GetThreadID(), SYS_NET_ABORT_STRICT_CHECK );
|
||||
}
|
||||
#endif
|
||||
|
||||
const uint k_uJoinTimeoutMillisec = 10000; // 10 seconds seems pretty arbitrary.
|
||||
|
||||
CWorkThread *pWorkThread = m_WorkThreads[0];
|
||||
bool bJoined = pWorkThread->Join( k_uJoinTimeoutMillisec );
|
||||
if ( !bJoined )
|
||||
{
|
||||
// Print thread id as a pointer for cross-platform compatibility
|
||||
EmitWarning( SPEW_THREADS, 2, "Thread \"%s\" (ID %p) failed to shut down", pWorkThread->GetName(), (void*)pWorkThread->GetThreadID() );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Succesful join means that the thread has terminated.
|
||||
if ( !pWorkThread->m_bFinished )
|
||||
{
|
||||
// This would be a logic error in the thread proc if it ever tripped.
|
||||
AssertMsg( false, "pWorkThread->m_bFinished is false but thread is not running" );
|
||||
// Recover by flagging the thread as potentially eligable for deletion, since it's dead.
|
||||
pWorkThread->m_bFinished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert( m_WorkThreads.Count() == 0 && m_cThreadsRunning == (uint32) 0 );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: sees if we have a non-zero number of work threads,
|
||||
// or a non-zero number of active threads
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CWorkThreadPool::HasWorkItemsToProcess() const
|
||||
{
|
||||
return ( m_pTSQueueToProcess->Count() > 0 )
|
||||
|| ( m_cActiveThreads > 0 );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: sets dynamic thread construction
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWorkThreadPool::SetWorkThreadAutoConstruct( int cMaxThreads, IWorkThreadFactory *pWorkThreadConstructor )
|
||||
{
|
||||
AUTO_LOCK( m_WorkThreadMutex );
|
||||
|
||||
m_bThreadsInitialized = true;
|
||||
m_bAutoCreateThreads = true;
|
||||
m_cMaxThreads = MAX( 1, cMaxThreads );
|
||||
m_pWorkThreadConstructor = pWorkThreadConstructor;
|
||||
|
||||
// If we have too many threads now, mark some to exit next time they loop.
|
||||
for ( int i = m_cMaxThreads; i < m_WorkThreads.Count(); i++ )
|
||||
{
|
||||
m_WorkThreads[i]->m_bExitThread = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Adds a work item
|
||||
// Output: true if successful,
|
||||
// false if a low priority work item is not added due to a busy system
|
||||
// false if this work pool is shutting down and work isn't being accepted
|
||||
// NOTE: Adding normal priority items should always succeed
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CWorkThreadPool::AddWorkItem( CWorkItem *pWorkItem )
|
||||
{
|
||||
Assert( !m_bExiting );
|
||||
if ( m_bExiting )
|
||||
return false;
|
||||
|
||||
if ( m_bEnsureOutputOrdering )
|
||||
{
|
||||
AssertMsg( pWorkItem->m_bResubmit == false, "CWorkThreadPool can't support item auto resubmission when ensuring output ordering" );
|
||||
}
|
||||
|
||||
// if we're in auto-create mode, make sure we have enough threads running
|
||||
if ( m_bAutoCreateThreads && m_WorkThreads.Count() < m_cMaxThreads )
|
||||
{
|
||||
int cPendingItems = m_pTSQueueToProcess->Count();
|
||||
|
||||
// we shouldn't get more than 12 items queued per already existing thread, otherwise we
|
||||
// want to create a new thread to help us keep up.
|
||||
if ( m_WorkThreads.Count() < 1 || m_WorkThreads.Count() * 12 < ( cPendingItems + 1 ) )
|
||||
{
|
||||
if ( m_WorkThreads.Count() >= 2 && !m_LimitTimerCreateNewThreads.BLimitReached() )
|
||||
{
|
||||
// Don't create more yet, we don't want to create them too fast
|
||||
}
|
||||
else
|
||||
{
|
||||
// create another thread
|
||||
CWorkThread *pWorkThread = NULL;
|
||||
if ( m_pWorkThreadConstructor )
|
||||
{
|
||||
pWorkThread = m_pWorkThreadConstructor->CreateWorkerThread( this );
|
||||
}
|
||||
else
|
||||
{
|
||||
pWorkThread = new CWorkThread( this );
|
||||
}
|
||||
if( pWorkThread != NULL )
|
||||
{
|
||||
int iName = AddWorkThread( pWorkThread );
|
||||
StartWorkThread( pWorkThread, iName );
|
||||
}
|
||||
m_LimitTimerCreateNewThreads.SetLimit( 250*k_nThousand );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Do we actually have any threads ? If creating threads can fail, then maybe we don't !
|
||||
// In that case, this WorkItem is not going to run !
|
||||
//
|
||||
if ( m_WorkThreads.Count() == 0 )
|
||||
{
|
||||
Assert(false);
|
||||
return false ;
|
||||
}
|
||||
|
||||
|
||||
// WARNING: We need to call pWorkItem AddRef() and Release() at all entry/exit points for the thread pool system.
|
||||
pWorkItem->AddRef();
|
||||
|
||||
pWorkItem->m_ulSequenceNumber = (++m_ulLastUsedSequenceNumber);
|
||||
m_pTSQueueToProcess->PushItem( pWorkItem );
|
||||
|
||||
if ( !BNeverSetEventOnAdd() && m_cActiveThreads == 0 )
|
||||
{
|
||||
VPROF_BUDGET( "SetEvent()", VPROF_BUDGETGROUP_THREADINGMAIN );
|
||||
m_EventNewWorkItem.Set();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
CWorkItem *CWorkThreadPool::GetNextCompletedWorkItem( )
|
||||
{
|
||||
CWorkItem *pWorkItem = NULL;
|
||||
|
||||
// Use a while loop just in case ref counts get screwed up and an item gets deleted when we release our reference to it
|
||||
while ( m_pTSQueueCompleted->PopItem( &pWorkItem ) )
|
||||
{
|
||||
// WARNING: We need to call workitem AddRef() and Release() at all entry/exit points for the thread pool system.
|
||||
// Release() returns the current refcount of the object (after decrementing it by one) and should be non-zero unless the
|
||||
// the caller has released it already.
|
||||
if ( pWorkItem != NULL && pWorkItem->Release() > 0 )
|
||||
{
|
||||
return pWorkItem;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: gets the next work item to process. This non-blocking function
|
||||
// returns NULL immediately if there's nothing left in the queue.
|
||||
// otherwise, a pointer to the next CWorkItem.
|
||||
//-----------------------------------------------------------------------------
|
||||
CWorkItem *CWorkThreadPool::GetNextWorkItemToProcess( )
|
||||
{
|
||||
CWorkItem *pWorkItem = NULL;
|
||||
|
||||
if ( m_pTSQueueToProcess->Count() && m_pTSQueueToProcess->PopItem( &pWorkItem ) )
|
||||
{
|
||||
return pWorkItem;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
bool CWorkThreadPool::BDispatchCompletedWorkItems( const CLimitTimer &limitTimer, CJobMgr *pJobMgr )
|
||||
{
|
||||
BTryDeleteExitedWorkerThreads();
|
||||
|
||||
CWorkItem *pWorkItem = GetNextCompletedWorkItem( );
|
||||
while ( pWorkItem != NULL )
|
||||
{
|
||||
uint64 ulSequenceNumber = pWorkItem->m_ulSequenceNumber;
|
||||
// NOTE: despite its name, this YIELDS - the target job
|
||||
// is resumed, and we resume here.
|
||||
if ( !pWorkItem->DispatchCompletedWorkItem( pJobMgr ) )
|
||||
{
|
||||
EmitWarning( SPEW_THREADS, 2, "Work Item for Work Pool %s completed but job no longer existed to notify\n", m_szThreadNamePfx == NULL ? "UNKNOWN" :m_szThreadNamePfx );
|
||||
AssertMsg1( m_bMayHaveJobTimeouts, "Work Item for Work Pool %s completed but job no longer existed to notify", m_szThreadNamePfx == NULL ? "UNKNOWN" :m_szThreadNamePfx );
|
||||
}
|
||||
|
||||
// pWorkItem was released by DispatchCompletedWorkItem
|
||||
m_ulLastDispatchedSequenceNumber = ulSequenceNumber;
|
||||
if ( limitTimer.BLimitReached() )
|
||||
break;
|
||||
|
||||
pWorkItem = GetNextCompletedWorkItem( );
|
||||
}
|
||||
|
||||
return ( GetCompletedWorkItemCount() > 0 );
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: delete any thread objects that have exited
|
||||
// we'll make sure the thread has actually ended;
|
||||
// if they haven't, they'll remain in the threads to delete list
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CWorkThreadPool::BTryDeleteExitedWorkerThreads()
|
||||
{
|
||||
if ( m_WorkThreadMutex.TryLock() )
|
||||
{
|
||||
if ( m_cThreadsRunning < (uint) m_WorkThreads.Count() )
|
||||
{
|
||||
FOR_EACH_VEC_BACK( m_WorkThreads, i )
|
||||
{
|
||||
CWorkThread *pWorkThread = m_WorkThreads[i];
|
||||
if ( pWorkThread->m_bFinished && !pWorkThread->IsThreadRunning() )
|
||||
{
|
||||
m_WorkThreads.FastRemove( i );
|
||||
delete pWorkThread;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_WorkThreadMutex.Unlock();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool CWorkItem::DispatchCompletedWorkItem( CJobMgr *pJobMgr )
|
||||
{
|
||||
// Check if this work item needs to signal a job
|
||||
if ( pJobMgr && k_GIDNil != m_JobID )
|
||||
{
|
||||
if ( !pJobMgr->BRouteWorkItemCompletedIfExists( m_JobID, m_bCanceled ) )
|
||||
return false;
|
||||
}
|
||||
else if ( k_GIDNil != m_JobID )
|
||||
{
|
||||
// This should never happen since we have already released our reference to the work item
|
||||
// and the calling job should have released its ref when it exited
|
||||
AssertMsg( false, "CWorkItem::DispatchCompletedWorkItem: got a work item with no job ID" );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Called by the worker thread when it finishes an individual work item
|
||||
// This function will see if our work is meant to be well-ordred; if so,
|
||||
// it will do the necessary work to ensure ordering.
|
||||
//
|
||||
// It adds the item to the completed work item list so
|
||||
// the pool owner can retrieve it and checks to see if any threads
|
||||
// deserve to be shut down.
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWorkThreadPool::OnWorkItemCompleted( CWorkItem *pWorkItem )
|
||||
{
|
||||
if ( sm_pWorkItemsCompletedSignal != NULL )
|
||||
sm_pWorkItemsCompletedSignal->Signal();
|
||||
|
||||
if ( !m_bEnsureOutputOrdering )
|
||||
{
|
||||
// Since we aren't locking this sequence number could get screwed up a bit, but it's
|
||||
// pretty meaningless if ensure output ordering if off anyway...
|
||||
m_ulLastCompletedSequenceNumber = pWorkItem->m_ulSequenceNumber;
|
||||
m_pTSQueueCompleted->PushItem( pWorkItem );
|
||||
}
|
||||
else
|
||||
{
|
||||
// In the ordered case we need to lock completely here since we'll be moving around between
|
||||
// various data structures and also need to ensure the ordering of items in the TS queue
|
||||
m_MutexOnItemCompletedOrdered.Lock();
|
||||
if ( m_ulLastCompletedSequenceNumber + 1 == pWorkItem->m_ulSequenceNumber )
|
||||
{
|
||||
m_ulLastCompletedSequenceNumber = pWorkItem->m_ulSequenceNumber;
|
||||
m_pTSQueueCompleted->PushItem( pWorkItem );
|
||||
|
||||
// We walk the vector multiple times, but it should be very short as items are likely to come in
|
||||
// close to in order, just mixed up a little if we have lots of threads or one item is much more
|
||||
// costly than others.
|
||||
bool bFoundNext = false;
|
||||
do
|
||||
{
|
||||
bFoundNext = false;
|
||||
FOR_EACH_VEC( m_vecCompletedAndWaiting, i )
|
||||
{
|
||||
CWorkItem *pWaiting = m_vecCompletedAndWaiting[i];
|
||||
if ( m_ulLastCompletedSequenceNumber + 1 == pWaiting->m_ulSequenceNumber )
|
||||
{
|
||||
m_ulLastCompletedSequenceNumber = pWaiting->m_ulSequenceNumber;
|
||||
m_pTSQueueCompleted->PushItem( pWaiting );
|
||||
m_vecCompletedAndWaiting.FastRemove( i );
|
||||
bFoundNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while ( bFoundNext == true );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_vecCompletedAndWaiting.AddToTail( pWorkItem );
|
||||
}
|
||||
m_MutexOnItemCompletedOrdered.Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: return the count of items we've queued to process
|
||||
//-----------------------------------------------------------------------------
|
||||
int CWorkThreadPool::GetWorkItemToProcessCount() const
|
||||
{
|
||||
return m_pTSQueueToProcess->Count();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: return the count of items we've completed but not notified the consumer about
|
||||
//-----------------------------------------------------------------------------
|
||||
int CWorkThreadPool::GetCompletedWorkItemCount() const
|
||||
{
|
||||
int nCount = m_pTSQueueCompleted->Count();
|
||||
return nCount;
|
||||
}
|
||||
|
||||
|
||||
#ifdef DBGFLAG_VALIDATE
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Validates memory
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWorkThreadPool::Validate( CValidator &validator, const char *pchName )
|
||||
{
|
||||
VALIDATE_SCOPE();
|
||||
AUTO_LOCK( m_WorkThreadMutex );
|
||||
|
||||
ValidateObj( m_WorkThreads );
|
||||
FOR_EACH_VEC( m_WorkThreads, iWorkThread )
|
||||
{
|
||||
m_WorkThreads[ iWorkThread ]->Suspend();
|
||||
ValidatePtr( m_WorkThreads[ iWorkThread ] );
|
||||
}
|
||||
|
||||
ValidateAlignedPtr( m_pTSQueueCompleted );
|
||||
ValidateAlignedPtr( m_pTSQueueToProcess );
|
||||
ValidateObj( m_vecCompletedAndWaiting );
|
||||
FOR_EACH_VEC( m_vecCompletedAndWaiting, j )
|
||||
{
|
||||
ValidatePtr( m_vecCompletedAndWaiting.Element( j ) );
|
||||
}
|
||||
|
||||
FOR_EACH_VEC( m_WorkThreads, iWorkThread )
|
||||
{
|
||||
m_WorkThreads[ iWorkThread ]->Resume();
|
||||
}
|
||||
|
||||
#if 0
|
||||
ValidateObj( m_StatExecutionTime );
|
||||
ValidateObj( m_StatWaitTime );
|
||||
#endif
|
||||
}
|
||||
#endif // DBGFLAG_VALIDATE
|
||||
|
||||
} // namespace GCSDK
|
||||
Reference in New Issue
Block a user