So, recently I made a leaderboard system for my game, but I was wondering how more popular games and bigger developers do their datastores, I doubt they do it the same way a beginner’s tutorial does it. Here is the code:
local rs = game:GetService("ReplicatedStorage")
local st = rs:WaitForChild("SaveTime")
local dss = game:GetService("DataStoreService")
local leaderboardstore = dss:GetOrderedDataStore("leaderboardstore10")
local playertime
local update
local function waitfor(reqType)
local cur = dss:GetRequestBudgetForRequestType(reqType)
while cur < 1 do
cur = dss:GetRequestBudgetForRequestType(reqType)
wait(5)
end
end
st.OnServerEvent:Connect(function(player, runtime)
playertime = tonumber(runtime)
print("before"..playertime)
playertime *= 100
playertime = math.floor(playertime + 0.5)
print("after"..playertime)
local userid = player.UserId
local key = userid
local success, err
repeat
waitfor(Enum.DataStoreRequestType.UpdateAsync)
success, err = pcall(leaderboardstore.UpdateAsync,leaderboardstore,key,function()
return playertime
end)
until success
if not success then
print(err)
warn(err)
end
end)
update = function()
local Data
local success, err
repeat
waitfor(Enum.DataStoreRequestType.GetSortedAsync)
success, err = pcall(function()
Data = leaderboardstore:GetSortedAsync(true,10)
end)
until success
if not success then
print(err)
end
local timepage = Data:GetCurrentPage()
for rank, data in ipairs(timepage) do
print(data.key)
if tonumber(data.key) < 1 then continue end
print(tonumber(data.key))
local username = game.Players:GetNameFromUserIdAsync(tonumber(data.key))
local name = username
local timing = data.value
local onboard = false
for i, v in pairs(game.Workspace.Leaderboard.LeaderboardGui.LeaderboardHolder:GetChildren()) do
if v.Player.Text == name then
onboard = true
break
end
end
if timing and onboard == false then
local split = tostring(timing/100):split(".")
local newframe = rs:WaitForChild("Template"):Clone()
newframe.Player.Text = name
newframe.Time.Text = string.format("%.2d.%.2d", tonumber(split[1]), tonumber(split[2]))
newframe.Rank.Text = "#"..rank
newframe.Position = UDim2.new(0,0,newframe.Position.Y.Scale + (.1 * #game.Workspace.Leaderboard.LeaderboardGui.LeaderboardHolder:GetChildren()), 0)
newframe.Parent = game.Workspace.Leaderboard.LeaderboardGui.LeaderboardHolder
end
end
end
while true do
for _, frame in pairs(game.Workspace.Leaderboard.LeaderboardGui.LeaderboardHolder:GetChildren()) do
frame:Destroy()
end
update()
print("update")
wait(10)
end
Also is this datastore secure enough? I tried adding pcalls and retries, but Im not sure if its enough.
How often does that happen though, I’m not sure if switching would be worth it since im not expecting over 100k-200k visits, thats just to be safe though in reality im expecting maybe 50k - 100k.
ehh, its probably likely thats why there’s mainly a pcall function just incase it catches for errors because there is a slight chance it might mess up and plus i think it’s just confusing overall
Ive included pcalls and retries, so are you saying roblox itself often corrupts or loses data, and I can do nothing about it? If so do you know how often it has happened in the past? Im a new developer so I dont really know how reliable it is.
i’d say its probably reliable enough to make a simple game, but unless you’re making an ambitious project you should probably use profile service cause it solves a number of problems
The newer major datastore update fixes a lot of the issues mentioned above with data loss and such, as versioning has been implemented. The above statements are (mostly) invalid. See: Data Stores | Roblox Creator Documentation
To answer the main question: Most well programmed systems use more complex data schema to save a lot of data at the same time as a part of their system, metadata tagging, etc. etc., (as well as better general code organization and such,) but fundamentally they are doing the same thing you are doing here. Some do, as mentioned, used pre-made services such as ProfileService or Datastore2.
There are a few issues I notice straight away, once of which within your waitfor() function, as you are setting the value of cur before performing the wait. (Use task.wait() by the way, blah blah better performance). The result of this is that if you have hit the limit, you will be waiting a minimum of 10 seconds before continuing as cur will be set to the exact same thing as it was before the start of the loop.
Another thing of note is that, assuming you continue to destroy your UI elements each update, you should be destroying your previous UI elements after you make your datastore call. This will make it appear to update without as much of a gap, though, it’d be better to update elements dynamically. This is more complex though, so I wouldn’t bother for now. Just some re-ordering can go a long way in making the UX better, though.
You are performing no validation of the data sent to the OnServerEvent call, allowing any exploiter to send any number to the remote event which will be saved in the datastore, which would set their data to whatever value they decided.
Besides this, your datastore setup itself seems to be good enough:tm: . There may be some other issues with the code that I can’t spot here, but this is what I notice in a short glance.
Thanks, I changed the wait in the waitfor function, also I was planning on adding some vaildation to protect against exploits soon, just wanted to check the actual datastore system itself for now. Anyways I didn’t know about the new datastore update, I’m most likely just going to be using the normal datastores now, so thanks for that as well.
ProfileService is not appropriate for Leaderboards, as it does not deal with OrderedDataStore.
OrderedDataStore lets you get the top scores, but then correlating that with the names to display, is the hard part. You’ll need to use GetUserInfosByUserIdsAsync to fetch the names in bulk. Because these can be expensive calls, you’ll need a module script or class that can retain the fetched information for a period of time before refreshing it.