So I'm working with Steamworks (leaderboards) and i have some strange issue. When i fire my function to get scores, from debugging i know that it works just fine.However my array after 1st function run always returns default values.After I fire function for the second time everything works perfectly fine. I tried to track down the issue however i failed.
Here is my whole code that i am using in this case:
Struct for stats
USTRUCT(BlueprintType)
struct FScorePackage
{
GENERATED_BODY()
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Leaderboard")
FString PlayerName = "working";
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Leaderboard")
int32 Rank = 0;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Leaderboard")
int32 Score = 0;
};
Function that sent request to the steam: .h
UFUNCTION(BlueprintCallable, Category = "Steam|Leaderboard", meta = (Latent, LatentInfo = "LatentInfo", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"))
TArray<FScorePackage> DownloadScoresAroundUser(UObject* WorldContextObject, int AboveUser, int BelowUser, struct FLatentActionInfo LatentInfo);
.cpp
TArray<FScorePackage> USteamLeaderboard::DownloadScoresAroundUser(UObject* WorldContextObject, int AboveUser, int BelowUser, struct FLatentActionInfo LatentInfo)
{
if (!m_CurrentLeaderboard)
{
return Scores;
}
if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject))
{
FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
if (LatentActionManager.FindExistingAction<SteamLeaderboardLatentClass>(LatentInfo.CallbackTarget, LatentInfo.UUID) == NULL)
{
// load the specified leaderboard data around the current user
SteamAPICall_t hSteamAPICall = SteamUserStats()->DownloadLeaderboardEntries(m_CurrentLeaderboard, k_ELeaderboardDataRequestGlobalAroundUser, -AboveUser, BelowUser);
m_callResultDownloadScore.Set(hSteamAPICall, this,&USteamLeaderboard::OnDownloadScore);
LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new SteamLeaderboardLatentClassScores(LatentInfo));
return Scores;
}
return Scores;
}
return Scores;
}
Now callback function from steam: .h
void OnDownloadScore(LeaderboardScoresDownloaded_t *pResult, bool bIOFailure);
CCallResult <USteamLeaderboard, LeaderboardScoresDownloaded_t> m_callResultDownloadScore;
.cpp
void USteamLeaderboard::OnDownloadScore(LeaderboardScoresDownloaded_t *pCallback, bool bIOFailure)
{
if (!bIOFailure)
{
m_nLeaderboardEntries = __min(pCallback->m_cEntryCount, 30);
for (int index = 0; index < m_nLeaderboardEntries; index++)
{
SteamUserStats()->GetDownloadedLeaderboardEntry(pCallback->m_hSteamLeaderboardEntries, index, &m_leaderboardEntries[index], NULL, 0);
}
TranslateEntries();
scores = true;
}
}
And finally function that write scores in Array:
.h
UFUNCTION(BlueprintCosmetic, Category = "Steam|Leaderboard")
TArray<FScorePackage> TranslateEntries();
.cpp
TArray<FScorePackage> USteamLeaderboard::TranslateEntries()
{
FScorePackage ThisScore;
Scores.Init(ThisScore, 30);
for (int i = 0; i < 30; i++)
{
ThisScore.PlayerName = GetSteamName(m_leaderboardEntries[i].m_steamIDUser);
ThisScore.Rank = m_leaderboardEntries[i].m_nGlobalRank;
ThisScore.Score = m_leaderboardEntries[i].m_nScore;
Arrayas[i] = ThisScore;
}
return Scores;
}
Scores array is just static TArray Scores and scores=true is only for latent check to go on with functions after calling DownloadScoresAroundUser :)
My normal flow with this is: 1.I already have handle for leaderboard. 2.I'm calling DownloadScoresAroundUser. 3.Flow goes to latent which cannot proceed becouse of scores=false. 4.After i got callback from steam OnDownloadScore fires, giving me all needed info(checked if really and it does!). 5.Then i call TranslateEntries to get all scores with names and rank in Array. 6.Then I'm printing whole array (with break package in unreal) and get default values of my struct. 7.After i fire whole cycle again i get proper values.
If any further info is required let me know :)
This is a bit of a guess, but it appears that you have a latency issue. When you make the request to download the scores, this is a time consuming call that does not block. You set up a callback that will be called when the scores are ready, then return the existing empty Scores
object.
When you make your second call, enough time has passed for the scores to have download and Scores
to be populated, so it returns some scores.
Note that you have a potential race condition, where DownloadScoresAroundUser
can access (return) Scores
while your callback is populating that vector.
Here's one possible solution. Before the scores have completed loading, DownloadScoresAroundUser
returns an empty Score (or possibly one indicating that scores are being loaded). Once the scores have been loaded and Scores
populated, it will return those. Also, the callback (besides populating Scores
) can in some fashion notify the caller(s) of DownloadScoresAndUser
that new scores are available. They can respond to that by calling in again to get the updated scores and refresh the display.