Leaderboard for friends

In addition to the global leaderboard I have now in my game, I want to make a leaderboard that displays the high scores of all your friends so that you can see where you stand among your friends. I’m at a standstill - I know how to get their friends and all, and I’ll be able to display the information just fine, but I’m not sure how I’m supposed to handle up to 200 GetAsync requests for each player that joins. Most players won’t have 200 friends, but you have to handle for the worst case scenario. The only way I really see the logic going is: player joins game, gather list of their friends, gather list of those friends who have a high score in the game with GetAsync, and display that information. The only way I can think to handle this securely is to queue the calls being performed based on the request limit for the server, which isn’t hard but still seems very slow worse case scenario…the only other game I’ve seen do this, I think, is @SirMing’s Egg Farm Simulator. Any tips/advice on how to do this more efficiently?

6 Likes

Two things come to mind (I like the second one the most :ok_hand:):

First, maybe only add friends to the leaderboard that have played with player together in a server? That way, you can save their userID or something in a datastore on player instead of having to go through 200 people. Most people play with their actual friends in the same server anyway and that should definitely keep your data calls down.

Or make the board slowly load the info. Maybe when the player joins, load a cache of some sort? Then display an updated leaderboard once it is done reading the data stores. This is probably how the other people do it.

1 Like

Does anyone have any additional thoughts on how this can be done in Roblox?
There is definitely a need for this as friends leaderboards are now very popular in mobile games. It allows players to see how they are doing against their friends.

(Sorry to bump, but this is one of those features that would be great to have in Roblox games).

1 Like

This is a theory I have in mind (I’ve never done this), but there is a yielding function to get the available friends online for a player.

I think if you have a table, and update it when someone on that’s friends list isn’t in that table and become online, and then update it with data that’s stored in your game (if they’ve ever played your game), I think it would be possible.

2 Likes

Yes but the problem is that each player can have up to 200 friends. So I need to query the datastore for each individual record, which will quickly surpass the datastore data limits.

I don’t think the current Roblox datastore system is well suited for something like this.

1 Like

You’re right. Data stores are not powerful enough for this. Fetching a global leaderboard is one thing, but filtering results for only a specific grouping of players or only fetching results from a certain group is another. Fetching friend data for friend-based leaderboards means mass polling of DataStores for data and then doing that to update leaderboards every now and again, which is a fabulous way to destroy your request budget.

The only way I can see this being done reasonably is if you have your data on a third party service. It’d certainly be easier that way, but that depends on your implementation.

4 Likes

I like an update on this . I would really like to make something like this to motivate people . Could this be feasible with the new messaging service ?

1 Like

Has anyone found a more efficient way to handle this? I’m facing the same issue waiting for data loading is a bit much, especially since I have a refresh button that updates the leaderboard data. If anyone has a faster solution, I’d love to hear it!

1 Like

Hey I was also recently reading this post to try to find a solution, I’ve made a lot of progress on my own version since then. What have you personally tried so far?

2 Likes

I think I’ve optimized this as much as possible for my case! It now takes about 5 seconds to check a player with 200 friends, without any DataStore exhaustion errors. I implemented MemoryService and MessagingService to handle the data more efficiently, added coroutine-based multiprocessing for the GetAsync calls, and this seems to be the best approach so far. I’d love to hear any feedback or additional ideas!

local function GetPlayerDataTest(userId)
    local OldTime = tick()
    local cacheKey = tostring(userId)

    local CACHE_REFRESH_INTERVAL = 10 -- in seconds

    local success, cachedData = pcall(function()
        return friendLeaderboard:GetAsync(cacheKey) -- Sorted map froom memmory service
    end)


    if success and cachedData and (tick() - (cachedData.lastUpdated or 0) < CACHE_REFRESH_INTERVAL) then
        return cachedData 
    end

    local success, playerData = pcall(function()
        return GlobalLeaderBoardStore:GetAsync(cacheKey) -- normal datastore
    end)

    if success and playerData then
        local data = {
            UserId = userId,
            GarageWorth = playerData,
            lastUpdated = tick()
        }

        friendLeaderboard:SetAsync(cacheKey, data, CACHE_EXPIRY_TIME)
        MessagingService:PublishAsync("LeaderboardUpdate", {UserId = userId, Data = data}) -- using messaging service

        MessagingService:SubscribeAsync("LeaderboardUpdate", function(message)
            local userId = message.Data.UserId
            local data = message.Data.Data
            if userId and data then
                friendLeaderboard:SetAsync(tostring(userId), data, CACHE_EXPIRY_TIME)
            end
        end)

        return data
    end

    return nil
end

In this setup, I’m using a setup here for coroutine-based multiprocessing. Here’s a quick snippet of the code.

local function GetFriendDataBatch(startIndex, endIndex)
        for i = startIndex, endIndex do
            local friendInfo = friendsList[i]
            if friendInfo then
                local friendData = GetPlayerDataTest(friendInfo.Id)
                if friendData then
                    table.insert(FriendLeaderboard, friendData)
                end
            end
            task.wait(0.1)
        end
    end

    local totalFriends = #friendsList
    local startIndex = 1
    local threads = {}

    while startIndex <= totalFriends do
        local endIndex = math.min(startIndex, totalFriends) 
        local thread = coroutine.create(function()
            GetFriendDataBatch(startIndex, endIndex)
        end)
        table.insert(threads, thread)
        coroutine.resume(thread)
        startIndex = endIndex + 1
    end

    for _, thread in ipairs(threads) do
        while coroutine.status(thread) ~= "dead" do
            task.wait()
        end
    end
1 Like

But this uses 2 get asyncs, one publish async, and one subscribe async every 0.1 seconds? How does it not hit any limits

1 Like

Honestly, I’m not sure. I stress-tested it with 1,000 players and didn’t encounter any limit errors. My game might be single-player, but I don’t think that’s the reason. Have you run into any DataStore limits with my code?

Update: I’m also using MemoryService for the friend leaderboard, which has a much higher limit than DataStore. The friend leaderboard acts as a cache for me, so that might be why I’m not hitting any limits.

image
But here right away you’re using 2 GetAsync requests no? I haven’t tried your code to know if it hits any limits, but if it’s a singleplayer game then that would make sense, because each server has it’s own limits so one player can max out their own servers limits.

Try it with 10-20 player servers

Yeah, that’s probably it. My game is single-player, so that would likely explain it. Havent tryed it with 20 players in the server and i am sure it will hit a limit with 20.

Upadate: I tested it again, and after refreshing the leaderboard 3 or 4 times, I hit the DataStore limitso I have the same issue as you. Sorry for the confusion! Have you found a better solution for handling this?

I’m looking into storing the data from the entire leaderboard into a table and accessing that with their friend’s userid

1 Like