Notifications
Clear all

Filtering leaderboards by friends

(@punchey)
Eminent Member Registered

I am wondering what the best way is to display a leaderboard composed only of scores of the localUser and their friends, so they can compare how they are doing against their friends?

I've tried calling Leaderboard.SetUserFilter() with their friend list, and then call LoadScores() but that doesn't seem to filter the results. I still get normal results for the scores list.  If I'm supposed to call SetUserFilter() after LoadScores(), then I assume that means I need to load the entire leaderboard to make sure I don't miss any possible scores from friends. That seems very inefficient if the leaderboard has hundreds of thousands of scores on it (which hopefully eventually it will).

I realize I could also call LoadScoresByUser() for each friend separately, but that seems inefficient since if the user has hundreds of friends, that would result in hundreds of separate web requests, and I'm already experiencing bugs when making many web requests too close together.

Is there a more efficient solution?  Thanks!

ReplyQuote
Topic starter Posted : 22/03/2020 10:53 pm
(@punchey)
Eminent Member Registered

I just thought of one possible reason SetUserFilter() isn't working as I expected: I'm using userNames (not long ID number) for the list I pass to SetUserFilter(). I changed it to use the id instead of userName, but it still didn't work to call SetUserFilter() first, then LoadScores().  I also tried calling SetUserFilter() after LoadScores() and that didn't work either.

ReplyQuote
Topic starter Posted : 22/03/2020 11:19 pm
(@punchey)
Eminent Member Registered

Ah ha! I discovered the Leaderboard.userScope field, and by assigning UserScope.FriendsOnly, it worked!  Which leaves me with just one more question:

In the results, the .rank field of each score entry seems to be the rank of that score in the filtered results.  Is there a way to easily and efficiently get the global rank?  So for example, the results when displayed would look something like:

Global Rank    Friend    Score
11 Guy 1,000
23 John 750
53 Sarah 523

Is that possible without doing a lot of separate requests?

ReplyQuote
Topic starter Posted : 22/03/2020 11:38 pm
(@skaredcreations)
Prominent Member Admin

Unfortunately it isn't currently possible, the query to get it is a bit complex, I'll eventually try to optimize the query in a future update (but no ETA, may be it's not a trivial task; if you find a viable query I'll be glad to check it out, unfortunately I still didn't succeed).

For now you could optimize the loading of the global rank with a small trick through a coroutine:

foreach (var score in leaderboard.scores)
{
bool loaded = false;
int rank = 0;
leaderboard.LoadScoresByUser(long.Parse(score.userID), eLeaderboardInterval.Total, 10, (Score friendScore, int friendPage, string error) =>
{
rank = friendScore.rank;
loaded = true;
});
while (!loaded)
yield return new WaitForFixedUpdate();
// do what you need with "rank"
}

This way you make only one call at time instead of multiple concurrent calls (which would hit your web server queue). The number "10" should be the same you use to paginate the leaderboard scores (that is your leaderboard.range.count used in leaderboard.LoadScores).

FRANCESCO CROCETTI @ SKARED CREATIONS

ReplyQuote
Posted : 23/03/2020 12:47 am
(@punchey)
Eminent Member Registered

@skaredcreations thanks!  I can see how it would be non-trivial to implement. Thanks again!

 

ReplyQuote
Topic starter Posted : 23/03/2020 1:46 am
(@punchey)
Eminent Member Registered

I've run into an additional issue trying to do this. I may not have tested the above solution thoroughly enough.

I had test data for a single leaderboard, and I'm using string codes to identify leaderboards, rather than the integer ID.  Here's what I'm doing:

        var board = CombuManager.platform.CreateLeaderboard();
board.id = leaderboardCode;
board.timeScope = UnityEngine.SocialPlatforms.TimeScope.AllTime;
board.range = new UnityEngine.SocialPlatforms.Range(1, 1000);
board.userScope = UserScope.FriendsOnly;
...
board.LoadScores(...);

That seemed to work when I was just testing with the one leaderboard that had test data.  However, now I'm testing with a different leadeboardCode for a leaderboard that has no records in it yet.  Strangely, when doing that, the scores array has all my test friend scores from the one leaderboard that has data, even though that's not the leaderboard I queried!

When looking at the score records themselves, it even says "leaderboardID = 55" (which is the integer ID of the leaderboard I DID intend to query, but there shouldn't be any records in that leaderboard, and if I look at it through the Combu admin web interface, it is indeed empty.  Also, the leaderboardCode field is an empty string, rather than my string code ("ft_exp_addicted").

So in other words, it looks like it is returning a bunch of records from the wrong leaderboard, but saying they are from leaderboardID 55, which is the ID of the correct leaderboard -- which is empty.

I tried just manually assigning "55" to board.id to see if that would give the correct result (an empty score list), but it didn't.  No matter what I try, it gives me the scores from the one and only leaderboard I have that has test scores in it, which also happens to be the first leaderboard in the list (with ID 1). I don't know if that is a clue or not.

The local player score (board.localUserScore) appears to be correct (0 - since there shouldn't be any scores on that leaderboard). My best guess is there is a bug in leaderboards.php:wsHighscore()?

ReplyQuote
Topic starter Posted : 24/03/2020 4:10 am
(@punchey)
Eminent Member Registered

@skaredcreations I guess it may be something with how the query is being built in LeaderboarBoard.php. But I can't quite tell.

 

ReplyQuote
Topic starter Posted : 24/03/2020 5:46 am
(@skaredcreations)
Prominent Member Admin

I found the bug in the query, it's pretty strange that nobody reported it before (this make me think that the others never used the FriendsOnly feature):

at line 261 of Leaderboard.php replace this:

$where = sprintf("(IdAccount IN (%s))", implode(",", $filterId));

with this:

$where .= sprintf(" AND (IdAccount IN (%s))", implode(",", $filterId));
 
This fix will be added to the next update (I'll probably optimize this particular filter a bit with an INNER JOIN to not create a concatenated string of friend's Ids), thanks for your report.

FRANCESCO CROCETTI @ SKARED CREATIONS

ReplyQuote
Posted : 24/03/2020 2:42 pm
(@punchey)
Eminent Member Registered

@skaredcreations thanks so much! I'll apply this change ASAP.

 

ReplyQuote
Topic starter Posted : 24/03/2020 11:27 pm
(@punchey)
Eminent Member Registered

Just wanted to report success!  Thanks again!

ReplyQuote
Topic starter Posted : 25/03/2020 7:46 pm
Share: