Skip to content

Commit 94c7321

Browse files
authored
Merge pull request #568 from tintinhamans/arctic/ngmp-cache-fix
fix(ngmp): correct stats and media upload caching
2 parents 401414e + f29a26f commit 94c7321

6 files changed

Lines changed: 305 additions & 105 deletions

File tree

Core/GameEngine/Source/GameNetwork/GameSpy/Thread/PersistentStorageThread.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,11 @@ PSPlayerStats::PSPlayerStats()
10941094
void PSPlayerStats::reset()
10951095
{
10961096
id = 0;
1097+
#if defined(GENERALS_ONLINE)
1098+
elo_rating = 0;
1099+
monthly_elo_rating = 0;
1100+
elo_num_matches = 0;
1101+
#endif
10971102
locale = 0;
10981103
gamesAsRandom = 0;
10991104
lastFPS = 0;

GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -532,46 +532,39 @@ class NGMP_OnlineServicesManager
532532
m_vecCachedScreenshotBytes_MatchStart = vecData;
533533
}
534534

535-
void CacheScreenshotBytes_EndMatch(std::vector<uint8_t>& vecData)
536-
{
537-
std::scoped_lock<std::mutex> ssLock(m_ScreenshotMutex);
538-
m_vecCachedScreenshotBytes_MatchEnd = vecData;
539-
}
540-
541-
void CacheReplayBytes(std::vector<uint8_t>& vecData)
542-
{
543-
std::scoped_lock<std::mutex> ssLock(m_ScreenshotMutex);
544-
m_vecCachedReplayBytes = vecData;
545-
}
546-
535+
void CacheScreenshotBytes_EndMatch(uint64_t matchID, std::vector<uint8_t> data);
536+
void CacheReplayBytes(uint64_t matchID, std::vector<uint8_t> data);
547537
void SetScreenshotS3URI_StartMatch(const char* szURI)
548538
{
549539
std::scoped_lock<std::mutex> ssLock(m_ScreenshotMutex);
550540
m_strCachedScreenshot_MatchStart_S3URI = std::string(szURI);
551541
}
552542

553-
void SetScreenshotS3URI_EndMatch(const char* szURI)
554-
{
555-
std::scoped_lock<std::mutex> ssLock(m_ScreenshotMutex);
556-
m_strCachedScreenshot_MatchEnd_S3URI = std::string(szURI);
557-
}
558-
559-
void SetScreenshotS3URI_Replay(const char* szURI)
560-
{
561-
std::scoped_lock<std::mutex> ssLock(m_ScreenshotMutex);
562-
m_strCacheReplay_S3URI = std::string(szURI);
563-
}
543+
void SetScreenshotS3URI_EndMatch(uint64_t matchID, std::string uri);
544+
void SetScreenshotS3URI_Replay(uint64_t matchID, std::string uri);
564545

565546
private:
566547
// NOTE: Accessed from multiple threads, dont access directly, use helpers above to lock
567548
std::string m_strCachedScreenshot_MatchStart_S3URI;
568-
std::string m_strCachedScreenshot_MatchEnd_S3URI;
569-
std::string m_strCacheReplay_S3URI;
570549

571550
// screenshots / replays that require caching
572551
std::vector<uint8_t> m_vecCachedScreenshotBytes_MatchStart;
573-
std::vector<uint8_t> m_vecCachedScreenshotBytes_MatchEnd;
574-
std::vector<uint8_t> m_vecCachedReplayBytes;
552+
553+
struct CachedMatchUpload
554+
{
555+
uint64_t dataMatchID = 0;
556+
uint64_t uriMatchID = 0;
557+
std::vector<uint8_t> bytes;
558+
std::string signedURI;
559+
};
560+
561+
void CacheMatchUploadBytes(CachedMatchUpload& upload, uint64_t matchID, std::vector<uint8_t> data);
562+
void CacheMatchUploadURI(CachedMatchUpload& upload, uint64_t matchID, std::string uri);
563+
564+
// Data and URLs may arrive independently. Keeping their shared match ID here
565+
// prevents a late response from being paired with media from another match.
566+
CachedMatchUpload m_cachedMatchEndUpload;
567+
CachedMatchUpload m_cachedReplayUpload;
575568

576569
// main thread SS Upload
577570
static std::mutex m_ScreenshotMutex;

GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_StatsInterface.h

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
#include "GameNetwork/RankPointValue.h"
66
#include "GameNetwork/GameSpy/PersistentStorageThread.h"
77

8+
#include <chrono>
9+
#include <cstddef>
10+
#include <cstdint>
11+
812
class PSPlayerStats;
913

1014
struct GlobalStats
@@ -468,21 +472,37 @@ class NGMP_OnlineServices_StatsInterface
468472

469473
void findPlayerStatsByID(int64_t userID, std::function<void(bool, PSPlayerStats)> cb, EStatsRequestPolicy requestPolicy);
470474
void findPlayerStatsByBatch(std::vector<int64_t> vecUserIDs, std::function<void(bool)> cb);
475+
// Returns the last-known cached values, even when stale. Use
476+
// HasFreshPlayerStats() to decide whether a refresh is required.
471477
bool getPlayerStatsFromCache(int64_t userID, PSPlayerStats* outStats);
472-
bool ArePlayerStatsCached(int64_t userID)
473-
{
474-
return m_mapCachedStats.contains(userID);
475-
}
478+
bool HasFreshPlayerStats(int64_t userID);
476479

477-
void UpdateMyStats(PSPlayerStats stats);
480+
void UpdateMyStats(const PSPlayerStats& stats);
478481

479482
void CommitMyOutcome(ScoreKeeper* pScoreKeeper, bool bWon);
480483

481484
private:
482-
std::string JSONSerialize(PSPlayerStats stats);
485+
struct PlayerStatsCacheEntry
486+
{
487+
PSPlayerStats stats;
488+
std::chrono::steady_clock::time_point lastRefreshAt{};
489+
std::chrono::steady_clock::time_point lastAccessAt{};
490+
uint64_t cacheRevision = 0;
491+
uint64_t updateRevision = 0;
492+
bool hasStats = false;
493+
};
494+
495+
std::string JSONSerialize(const PSPlayerStats& stats) const;
496+
PlayerStatsCacheEntry& GetOrCreatePlayerStatsCacheEntry(int64_t userID);
497+
uint64_t AdvancePlayerStatsCacheRevision(int64_t userID);
498+
uint64_t AdvancePlayerStatsUpdateRevision(int64_t userID);
499+
bool TryCachePlayerStats(const PSPlayerStats& stats, uint64_t expectedRevision);
500+
void MarkPlayerStatsCacheStale(int64_t userID);
483501

484502
private:
485-
std::unordered_map<int64_t, int64_t> m_mapStatsLastRefresh;
486-
std::unordered_map<int64_t, PSPlayerStats> m_mapCachedStats;
487-
const static int64_t m_cacheTTL = 600000; // 10 minutes
503+
std::unordered_map<int64_t, PlayerStatsCacheEntry> m_playerStatsCache;
504+
uint64_t m_nextStatsCacheRevision = 0;
505+
uint64_t m_nextStatsUpdateRevision = 0;
506+
static constexpr std::chrono::minutes STATS_CACHE_TTL{ 10 };
507+
static constexpr std::size_t MAX_PLAYER_STATS_CACHE_ENTRIES = 128;
488508
};

GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,7 @@ void PopulateLobbyPlayerListbox()
754754
{
755755
NetworkRoomMember& netRoomMember = kvPair.second;
756756

757-
if (!pStatsInterface->ArePlayerStatsCached(netRoomMember.user_id))
757+
if (!pStatsInterface->HasFreshPlayerStats(netRoomMember.user_id))
758758
{
759759
vecUserStatsToRequest.push_back(netRoomMember.user_id);
760760
}

GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "WW3D2/surfaceclass.h"
1616
#include "WW3D2/dx8wrapper.h"
1717
#include <mutex>
18+
#include <utility>
1819

1920
#define STB_IMAGE_WRITE_IMPLEMENTATION
2021
#define STB_IMAGE_RESIZE_IMPLEMENTATION
@@ -118,7 +119,8 @@ void NGMP_OnlineServicesManager::CaptureScreenshotForProbe(EScreenshotType scree
118119
NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface<NGMP_OnlineServices_LobbyInterface>();
119120
if (pLobbyInterface != nullptr)
120121
{
121-
NGMP_OnlineServicesManager::GetInstance()->CaptureScreenshot(true, [strURI, screenshotType](std::vector<uint8_t> vecData)
122+
const uint64_t matchID = pLobbyInterface->GetCurrentMatchID();
123+
NGMP_OnlineServicesManager::GetInstance()->CaptureScreenshot(true, [strURI = std::move(strURI), screenshotType, matchID](std::vector<uint8_t> vecData)
122124
{
123125
CHECK_WORKER_THREAD;
124126

@@ -135,7 +137,7 @@ void NGMP_OnlineServicesManager::CaptureScreenshotForProbe(EScreenshotType scree
135137
}
136138
else if (screenshotType == EScreenshotType::SCREENSHOT_TYPE_SCORESCREEN)
137139
{
138-
NGMP_OnlineServicesManager::GetInstance()->CacheScreenshotBytes_EndMatch(vecData);
140+
NGMP_OnlineServicesManager::GetInstance()->CacheScreenshotBytes_EndMatch(matchID, std::move(vecData));
139141
}
140142
else
141143
{
@@ -248,6 +250,13 @@ void NGMP_OnlineServicesManager::CommitReplay(AsciiString absoluteReplayPath)
248250

249251
if (serviceConf.do_replay_upload)
250252
{
253+
NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface<NGMP_OnlineServices_LobbyInterface>();
254+
const uint64_t matchID = pLobbyInterface == nullptr ? 0 : pLobbyInterface->GetCurrentMatchID();
255+
if (matchID == 0)
256+
{
257+
NetworkLog(ELogVerbosity::LOG_RELEASE, "[MediaUpload] Cannot cache replay: match ID is unavailable");
258+
return;
259+
}
251260
FILE* pFile = fopen(absoluteReplayPath.str(), "rb");
252261

253262
std::vector<unsigned char> replayData;
@@ -265,11 +274,55 @@ void NGMP_OnlineServicesManager::CommitReplay(AsciiString absoluteReplayPath)
265274
}
266275

267276
// cache the data until we get an S3 URL from server
268-
NGMP_OnlineServicesManager::GetInstance()->CacheReplayBytes(replayData);
277+
NGMP_OnlineServicesManager::GetInstance()->CacheReplayBytes(matchID, std::move(replayData));
269278
}
270279
}
271280
}
272281

282+
void NGMP_OnlineServicesManager::CacheMatchUploadBytes(CachedMatchUpload& upload, uint64_t matchID, std::vector<uint8_t> data)
283+
{
284+
if (matchID == 0)
285+
{
286+
return;
287+
}
288+
289+
std::scoped_lock<std::mutex> ssLock(m_ScreenshotMutex);
290+
upload.dataMatchID = matchID;
291+
upload.bytes = std::move(data);
292+
}
293+
294+
void NGMP_OnlineServicesManager::CacheMatchUploadURI(CachedMatchUpload& upload, uint64_t matchID, std::string uri)
295+
{
296+
if (matchID == 0)
297+
{
298+
return;
299+
}
300+
301+
std::scoped_lock<std::mutex> ssLock(m_ScreenshotMutex);
302+
upload.uriMatchID = matchID;
303+
upload.signedURI = std::move(uri);
304+
}
305+
306+
void NGMP_OnlineServicesManager::CacheScreenshotBytes_EndMatch(uint64_t matchID, std::vector<uint8_t> data)
307+
{
308+
CacheMatchUploadBytes(m_cachedMatchEndUpload, matchID, std::move(data));
309+
}
310+
311+
void NGMP_OnlineServicesManager::CacheReplayBytes(uint64_t matchID, std::vector<uint8_t> data)
312+
{
313+
CacheMatchUploadBytes(m_cachedReplayUpload, matchID, std::move(data));
314+
}
315+
316+
void NGMP_OnlineServicesManager::SetScreenshotS3URI_EndMatch(uint64_t matchID, std::string uri)
317+
{
318+
CacheMatchUploadURI(m_cachedMatchEndUpload, matchID, std::move(uri));
319+
}
320+
321+
void NGMP_OnlineServicesManager::SetScreenshotS3URI_Replay(uint64_t matchID, std::string uri)
322+
{
323+
CacheMatchUploadURI(m_cachedReplayUpload, matchID, std::move(uri));
324+
}
325+
273326
void NGMP_OnlineServicesManager::WaitForScreenshotThreads()
274327
{
275328
std::scoped_lock<std::mutex> lock(m_mutexScreenshotThreads);
@@ -910,31 +963,30 @@ void NGMP_OnlineServicesManager::Tick()
910963
}
911964
}
912965

913-
if (!m_vecCachedScreenshotBytes_MatchEnd.empty()) // we have data waiting
966+
if (!m_cachedMatchEndUpload.bytes.empty()) // we have data waiting
914967
{
915-
if (!m_strCachedScreenshot_MatchEnd_S3URI.empty()) // and we have a URL
968+
if (m_cachedMatchEndUpload.dataMatchID == m_cachedMatchEndUpload.uriMatchID && !m_cachedMatchEndUpload.signedURI.empty()) // and we have a matching URL
916969
{
917970
// queue it
918971
S3ScreenshotEntry newEntry;
919972
newEntry.screenshotType = EScreenshotType::SCREENSHOT_TYPE_SCORESCREEN;
920-
newEntry.vecBytes = m_vecCachedScreenshotBytes_MatchEnd;
921-
newEntry.strSignedURI = m_strCachedScreenshot_MatchEnd_S3URI;
922-
m_vecGuardedSSData.push_back(newEntry);
973+
newEntry.vecBytes = std::move(m_cachedMatchEndUpload.bytes);
974+
newEntry.strSignedURI = std::move(m_cachedMatchEndUpload.signedURI);
975+
m_vecGuardedSSData.push_back(std::move(newEntry));
923976

924977
// clear data
925-
m_vecCachedScreenshotBytes_MatchEnd = std::vector<uint8_t>();
926-
m_strCachedScreenshot_MatchEnd_S3URI = std::string();
978+
m_cachedMatchEndUpload = {};
927979
}
928980
}
929981

930-
if (!m_vecCachedReplayBytes.empty()) // we have data waiting
982+
if (!m_cachedReplayUpload.bytes.empty()) // we have data waiting
931983
{
932-
if (!m_strCacheReplay_S3URI.empty()) // and we have a URL
984+
if (m_cachedReplayUpload.dataMatchID == m_cachedReplayUpload.uriMatchID && !m_cachedReplayUpload.signedURI.empty()) // and we have a matching URL
933985
{
934986
// do the upload
935987
std::map<std::string, std::string> mapHeaders;
936988
mapHeaders["Content-Type"] = "application/octet-stream";
937-
NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()->SendS3PUTRequest(m_strCacheReplay_S3URI.c_str(), EIPProtocolVersion::DONT_CARE, mapHeaders, m_vecCachedReplayBytes, [=](bool bSuccess, int statusCode, std::string strBody, HTTPRequest* pReq)
989+
NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()->SendS3PUTRequest(m_cachedReplayUpload.signedURI.c_str(), EIPProtocolVersion::DONT_CARE, mapHeaders, m_cachedReplayUpload.bytes, [=](bool bSuccess, int statusCode, std::string strBody, HTTPRequest* pReq)
938990
{
939991
#if _DEBUG
940992
if (statusCode != 200)
@@ -947,8 +999,7 @@ void NGMP_OnlineServicesManager::Tick()
947999
}, nullptr, HTTP_UPLOAD_TIMEOUT);
9481000

9491001
// clear data
950-
NGMP_OnlineServicesManager::GetInstance()->m_vecCachedReplayBytes.clear();
951-
NGMP_OnlineServicesManager::GetInstance()->m_strCacheReplay_S3URI = std::string();
1002+
m_cachedReplayUpload = {};
9521003
}
9531004
}
9541005
}

0 commit comments

Comments
 (0)