//========= Copyright © 1996-2009, Valve Corporation, All rights reserved. ============// // // Purpose: // //=====================================================================================// #include "mm_framework.h" #include "leaderboards.h" #include "fmtstr.h" // NOTE: This has to be the last file included! #include "tier0/memdbgon.h" // // Definition of leaderboard request queue class // class CLeaderboardRequestQueue : public ILeaderboardRequestQueue { public: CLeaderboardRequestQueue(); ~CLeaderboardRequestQueue(); // ILeaderboardRequestQueue public: virtual void Request( KeyValues *pRequest ); virtual void Update(); public: KeyValues * GetFinishedRequest(); protected: CUtlVector< KeyValues * > m_arrRequests; bool m_bQueryRunning; KeyValues *m_pFinishedRequest; void OnStartNewQuery(); void OnSubmitQuery(); void OnQueryFinished(); void Cleanup(); protected: #ifdef _X360 // Leaderboard query data CUtlVector< XUID > m_arrXuids; CUtlVector< XUSER_STATS_SPEC > m_arrSpecs; CUtlBuffer m_bufResults; XOVERLAPPED m_xOverlapped; void ProcessResults( XUSER_STATS_READ_RESULTS const *pResults ); // Leaderboard description data CUtlVector< KeyValues * > m_arrViewDescriptions; KeyValues * FindViewDescription( DWORD dwViewId ); #elif !defined( NO_STEAM ) KeyValues *m_pViewDescription; CCallResult< CLeaderboardRequestQueue, LeaderboardFindResult_t > m_CallbackOnLeaderboardFindResult; void Steam_OnLeaderboardFindResult( LeaderboardFindResult_t *p, bool bError ); CCallResult< CLeaderboardRequestQueue, LeaderboardScoresDownloaded_t > m_CallbackOnLeaderboardScoresDownloaded; void Steam_OnLeaderboardScoresDownloaded( LeaderboardScoresDownloaded_t *p, bool bError ); void ProcessResults( LeaderboardEntry_t const &lbe ); #endif }; CLeaderboardRequestQueue g_LeaderboardRequestQueue; ILeaderboardRequestQueue *g_pLeaderboardRequestQueue = &g_LeaderboardRequestQueue; // // Implementation of leaderboard request queue class // CLeaderboardRequestQueue::CLeaderboardRequestQueue() : m_bQueryRunning( false ), #if !defined( _X360 ) && !defined( NO_STEAM ) m_pViewDescription( NULL ), #endif m_pFinishedRequest( NULL ) { } CLeaderboardRequestQueue::~CLeaderboardRequestQueue() { Cleanup(); } void CLeaderboardRequestQueue::Request( KeyValues *pRequest ) { if ( !pRequest ) return; DevMsg( "CLeaderboardRequestQueue::Request\n" ); KeyValuesDumpAsDevMsg( pRequest, 1 ); m_arrRequests.AddToTail( pRequest->MakeCopy() ); } void CLeaderboardRequestQueue::Update() { if ( m_bQueryRunning ) { #ifdef _X360 if ( XHasOverlappedIoCompleted( &m_xOverlapped ) ) { OnQueryFinished(); } #endif } else if ( m_arrRequests.Count() ) { OnStartNewQuery(); } else if ( m_arrRequests.NumAllocated() ) { Cleanup(); } } KeyValues * CLeaderboardRequestQueue::GetFinishedRequest() { return m_pFinishedRequest; } void CLeaderboardRequestQueue::OnQueryFinished() { #ifdef _X360 // Submit results for processing XUSER_STATS_READ_RESULTS const *pResults = ( XUSER_STATS_READ_RESULTS const * ) m_bufResults.Base(); if ( pResults && pResults->dwNumViews > 0 && pResults->pViews ) ProcessResults( pResults ); #endif // Query is no longer running m_bQueryRunning = false; DevMsg( "CLeaderboardRequestQueue::OnQueryFinished\n" ); KeyValuesDumpAsDevMsg( m_pFinishedRequest, 1 ); // Stuff the data into the players #ifdef _X360 for ( int iCtrlr = 0; iCtrlr < XUSER_MAX_COUNT; ++ iCtrlr ) { XUID xuid = 0; XUSER_SIGNIN_INFO xsi; if ( XUserGetSigninState( iCtrlr ) != eXUserSigninState_NotSignedIn && ERROR_SUCCESS == XUserGetSigninInfo( iCtrlr, XUSER_GET_SIGNIN_INFO_ONLINE_XUID_ONLY, &xsi ) && !(xsi.dwInfoFlags & XUSER_INFO_FLAG_GUEST) ) xuid = xsi.xuid; #else int iCtrlr = XBX_GetPrimaryUserId(); { XUID xuid = g_pPlayerManager->GetLocalPlayer( iCtrlr )->GetXUID(); #endif KeyValues *pUserViews = NULL; if ( xuid ) pUserViews = m_pFinishedRequest->FindKey( CFmtStr( "%llx", xuid ) ); if ( pUserViews ) { IPlayerLocal *pPlayer = g_pPlayerManager->GetLocalPlayer( iCtrlr ); if ( pPlayer ) (( PlayerLocal * ) pPlayer)->OnLeaderboardRequestFinished( pUserViews ); } } } void CLeaderboardRequestQueue::Cleanup() { #ifdef _X360 // Clear view descriptions while ( m_arrViewDescriptions.Count() ) { m_arrViewDescriptions.Head()->deleteThis(); m_arrViewDescriptions.FastRemove( 0 ); } m_arrXuids.Purge(); m_arrSpecs.Purge(); m_bufResults.Purge(); m_arrViewDescriptions.Purge(); #elif !defined( NO_STEAM ) if ( m_pViewDescription ) m_pViewDescription->deleteThis(); m_pViewDescription = NULL; #endif // Clear requests while ( m_arrRequests.Count() ) { m_arrRequests.Head()->deleteThis(); m_arrRequests.FastRemove( 0 ); } m_arrRequests.Purge(); // Clear finished result if ( m_pFinishedRequest ) m_pFinishedRequest->deleteThis(); m_pFinishedRequest = NULL; } void CLeaderboardRequestQueue::OnStartNewQuery() { // When we are starting a new query we need to get rid of the old finished result if ( m_pFinishedRequest ) m_pFinishedRequest->deleteThis(); m_pFinishedRequest = NULL; #if !defined( NO_STEAM ) && !defined( SWDS ) extern CInterlockedInt g_numSteamLeaderboardWriters; if ( g_numSteamLeaderboardWriters ) return; // yield to writers that can alter the leaderboard #endif DevMsg( "CLeaderboardRequestQueue::OnStartNewQuery preparing request...\n" ); #ifdef _X360 // Prepare the XUIDs first m_arrXuids.RemoveAll(); for ( DWORD k = 0; k < XBX_GetNumGameUsers(); ++ k ) { int iCtrlr = XBX_GetUserId( k ); XUSER_SIGNIN_INFO xsi; if ( XUserGetSigninState( iCtrlr ) != eXUserSigninState_NotSignedIn && ERROR_SUCCESS == XUserGetSigninInfo( iCtrlr, XUSER_GET_SIGNIN_INFO_ONLINE_XUID_ONLY, &xsi ) && !(xsi.dwInfoFlags & XUSER_INFO_FLAG_GUEST) ) { m_arrXuids.AddToTail( xsi.xuid ); DevMsg( " XUID: %s (%llx)\n", xsi.szUserName, xsi.xuid ); } } // Clear view descriptions while ( m_arrViewDescriptions.Count() ) { m_arrViewDescriptions.Head()->deleteThis(); m_arrViewDescriptions.FastRemove( 0 ); } // Prepare the spec m_arrSpecs.RemoveAll(); for ( int q = 0; q < m_arrRequests.Count(); ++ q ) { KeyValues *pRequest = m_arrRequests[q]; KeyValues::AutoDelete autodelete_pRequest( pRequest ); m_arrRequests.Remove( q -- ); char const *szViewName = pRequest->GetName(); KeyValues *pDescription = g_pMMF->GetMatchTitle()->DescribeTitleLeaderboard( szViewName ); if ( !pDescription ) { DevWarning( " View %s failed to allocate description!\n", szViewName ); } KeyValues::AutoDelete autodelete_pDescription( pDescription ); // See if we already have a request for this view DWORD dwViewId = pDescription->GetInt( ":id" ); for ( int k = 0; k < m_arrSpecs.Count(); ++ k ) { if ( m_arrSpecs[k].dwViewId == dwViewId ) { dwViewId = 0; break; } } // If we already have a request for this view, then continue if ( !dwViewId ) continue; // Otherwise add this view to the spec XUSER_STATS_SPEC xss = {0}; xss.dwViewId = dwViewId; Assert( !xss.dwNumColumnIds ); m_arrSpecs.AddToTail( xss ); pDescription->SetString( ":name", szViewName ); m_arrViewDescriptions.AddToTail( pDescription ); autodelete_pDescription.Assign( NULL ); // don't autodelete now DevMsg( " View: %s (%d)\n", szViewName, dwViewId ); } #elif !defined( NO_STEAM ) // Clear view descriptions if ( m_pViewDescription ) m_pViewDescription->deleteThis(); m_pViewDescription = NULL; for ( int q = 0; q < m_arrRequests.Count(); ++ q ) { KeyValues *pRequest = m_arrRequests[q]; KeyValues::AutoDelete autodelete_pRequest( pRequest ); m_arrRequests.Remove( q -- ); char const *szViewName = pRequest->GetName(); m_pViewDescription = g_pMMF->GetMatchTitle()->DescribeTitleLeaderboard( szViewName ); if ( !m_pViewDescription ) { DevWarning( " View %s failed to allocate description!\n", szViewName ); continue; } m_pViewDescription->SetString( ":name", szViewName ); SteamAPICall_t hCall = steamapicontext->SteamUserStats()->FindLeaderboard( szViewName ); m_CallbackOnLeaderboardFindResult.Set( hCall, this, &CLeaderboardRequestQueue::Steam_OnLeaderboardFindResult ); m_bQueryRunning = true; break; } #endif // Clean up all the requests in the queue DevMsg( "CLeaderboardRequestQueue::OnStartNewQuery - request prepared.\n" ); // Run the query OnSubmitQuery(); } void CLeaderboardRequestQueue::OnSubmitQuery() { #ifdef _X360 if ( m_arrXuids.Count() && m_arrSpecs.Count() ) { DWORD dwBytes = 0; DWORD ret = XUserReadStats( 0, m_arrXuids.Count(), m_arrXuids.Base(), m_arrSpecs.Count(), m_arrSpecs.Base(), &dwBytes, NULL, NULL ); if ( ret == ERROR_INSUFFICIENT_BUFFER ) { ZeroMemory( &m_xOverlapped, sizeof( m_xOverlapped ) ); m_bufResults.EnsureCapacity( dwBytes ); ret = XUserReadStats( 0, m_arrXuids.Count(), m_arrXuids.Base(), m_arrSpecs.Count(), m_arrSpecs.Base(), &dwBytes, ( PXUSER_STATS_READ_RESULTS ) m_bufResults.Base(), &m_xOverlapped ); } if ( ret == ERROR_IO_PENDING ) { DevMsg( "CLeaderboardRequestQueue::OnSubmitQuery - query submitted...\n" ); m_bQueryRunning = true; } else { DevWarning( "CLeaderboardRequestQueue::OnSubmitQuery - failed [code = %d, bytes = %d]\n", ret, dwBytes ); } } #endif } #ifdef _X360 void CLeaderboardRequestQueue::ProcessResults( XUSER_STATS_READ_RESULTS const *pResults ) { /* 901D41D61DC61 // XUID %llx { survival_c5m2_park { :rank = 923 // uint64 :rows = 999 // uint64 :rating = 600 // uint64 besttime = 600 // uint64 } ... more views ... } ... more users ... */ DevMsg( "LeaderboardRequestQueue: ProcessResults ( %d views )\n", pResults->dwNumViews ); for ( DWORD k = 0; k < pResults->dwNumViews; ++ k ) { XUSER_STATS_VIEW const *pView = &pResults->pViews[k]; Assert( pView ); KeyValues *pViewDesc = FindViewDescription( pView->dwViewId ); Assert( pViewDesc ); if ( !pViewDesc ) { Warning( "LeaderboardRequestQueue: ProcessResults has no view description for view %d!\n", pView->dwViewId ); continue; } char const *szViewName = pViewDesc->GetString( ":name" ); DevMsg( " Processing view %d ( %s ), total rows = %d\n", pView->dwViewId, szViewName, pView->dwTotalViewRows ); for ( DWORD r = 0; r < pView->dwNumRows; ++ r ) { XUSER_STATS_ROW const *pRow = &pView->pRows[r]; if ( !pRow->dwRank && !pRow->i64Rating ) { DevMsg( " Gamer %s (%llx) not in view\n", pRow->szGamertag, pRow->xuid ); continue; // gamer is not present in the leaderboard } DevMsg( " Gamer %s (%llx) data loaded: rank=%d, rating=%lld\n", pRow->szGamertag, pRow->xuid, pRow->dwRank, pRow->i64Rating ); // Gamer is present in the leaderboard and should be included in the results if ( !m_pFinishedRequest ) m_pFinishedRequest = new KeyValues( "Leaderboard" ); // Find or create the view for this gamer KeyValues *pUserInfo = m_pFinishedRequest->FindKey( CFmtStr( "%llx/%s", pRow->xuid, szViewName ), true ); // Set his rank and rating pUserInfo->SetUint64( ":rank", pRow->dwRank ); pUserInfo->SetUint64( ":rows", pView->dwTotalViewRows ); pUserInfo->SetUint64( ":rating", pRow->i64Rating ); if ( char const *szName = pViewDesc->GetString( ":rating/name", NULL ) ) { if ( szName[0] ) pUserInfo->SetUint64( szName, pRow->i64Rating ); } // Process additional columns that were retrieved Assert( !pRow->dwNumColumns ); // for ( DWORD c = 0; c < pRow->dwNumColumns; ++ c ) // { // XUSER_STATS_COLUMN const *pCol = pRow->pColumns[ c ]; // KeyValues *pColDesc = FindViewColumnDesc( pViewDesc, pCol->wColumnId ) // } } } DevMsg( "LeaderboardRequestQueue: ProcessResults finished.\n" ); } #elif !defined( NO_STEAM ) void CLeaderboardRequestQueue::ProcessResults( LeaderboardEntry_t const &lbe ) { /* 901D41D61DC61 // XUID %llx { survival_c5m2_park { :rank = 923 // uint64 :rows = 999 // uint64 :rating = 600 // uint64 besttime = 600 // uint64 } ... more views ... } ... more users ... */ KeyValues *pViewDesc = m_pViewDescription; Assert( pViewDesc ); if ( !pViewDesc ) { Warning( "LeaderboardRequestQueue: ProcessResults has no view description for view!\n" ); return; } char const *szViewName = pViewDesc->GetString( ":name" ); DevMsg( " Processing view %s\n", szViewName ); DevMsg( " Gamer data loaded: rank=%d, score=%d\n", lbe.m_nGlobalRank, lbe.m_nScore ); // Gamer is present in the leaderboard and should be included in the results if ( !m_pFinishedRequest ) m_pFinishedRequest = new KeyValues( "Leaderboard" ); // Find or create the view for this gamer KeyValues *pUserInfo = m_pFinishedRequest->FindKey( CFmtStr( "%llx/%s", g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID(), szViewName ), true ); // Set user score pUserInfo->SetUint64( pViewDesc->GetString( ":score" ), lbe.m_nScore ); DevMsg( "LeaderboardRequestQueue: ProcessResults finished.\n" ); } #endif #ifdef _X360 KeyValues * CLeaderboardRequestQueue::FindViewDescription( DWORD dwViewId ) { if ( !dwViewId ) return NULL; for ( int k = 0; k < m_arrViewDescriptions.Count(); ++ k ) { KeyValues *pDesc = m_arrViewDescriptions[k]; if ( pDesc->GetInt( ":id" ) == ( int ) dwViewId ) return pDesc; } return NULL; } #elif !defined( NO_STEAM ) void CLeaderboardRequestQueue::Steam_OnLeaderboardFindResult( LeaderboardFindResult_t *p, bool bError ) { if ( bError || !p->m_bLeaderboardFound ) { DevMsg( "Steam leaderboard was not found.\n" ); OnQueryFinished(); return; } // Download the data SteamAPICall_t hCall = steamapicontext->SteamUserStats()->DownloadLeaderboardEntries( p->m_hSteamLeaderboard, k_ELeaderboardDataRequestGlobalAroundUser, 0, 0 ); m_CallbackOnLeaderboardScoresDownloaded.Set( hCall, this, &CLeaderboardRequestQueue::Steam_OnLeaderboardScoresDownloaded ); } void CLeaderboardRequestQueue::Steam_OnLeaderboardScoresDownloaded( LeaderboardScoresDownloaded_t *p, bool bError ) { // Fetch the data if found and no error LeaderboardEntry_t lbe; if ( !bError && p->m_cEntryCount == 1 && steamapicontext->SteamUserStats()->GetDownloadedLeaderboardEntry( p->m_hSteamLeaderboardEntries, 0, &lbe, NULL, 0 ) ) { ProcessResults( lbe ); } OnQueryFinished(); } #endif