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?
Two things come to mind (I like the second one the most ):
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.
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).
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.
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.
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.
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 ?
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!
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?
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
But this uses 2 get asyncs, one publish async, and one subscribe async every 0.1 seconds? How does it not hit any limits
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.
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