I’ve seen people say this over and over again, it’s simple to autosave people’s data but wouldn’t it make requests to be throttled more likely? Even if it’s just a bit.
If you save properly when the player leaves + using bindtoclose, then what’s the need?
If the server crashes for whatever reason or roblox servers are down and your datastore call doesnt go through in time before the server closes then that data is just gone
AFAIK bind to close doesn’t fire if the server crashes. It also has a maximum allowed yield time before the server closes anyways, regardless of if your script finished running or not. If roblox servers are having trouble it could be possible your data doesnt save fast enough
Originally, this is what I had. A regular datastore with all the player scores and orderedstores which would just have copies of that data.
However, an issue came where i would get throttling warnings and the leaderboards would load very slowly as I had to copy over 39 keys of data over to the orderedstores. This would be even more of a problem if I had more keys of data if my game grew.
So I permenantly copied over the regular datastore’s data to the orderedstores data and now the numeric scores of data for players is only in the orderedstores.
It’s hard to understand, but maybe this topic I made yesterday will help you understand what I mean:
The way mine is working it’s very light. It’s only doing 5 and that actually gets updated when they log in, off or die. Just a quick check if they are even in the top 5 then an update. But not to the datastore just the high score board. That like I said gets updated every 60 seconds IF it changed.
What i’m doing to update mine is updating every single key.
Here is the full code for reference:
local Players = game:GetService("Players")
local Datastoreservice = game:GetService("DataStoreService")
local DonatedStore = Datastoreservice:GetOrderedDataStore("DonatedData")
local RobuxSpentStore = Datastoreservice:GetOrderedDataStore("RobuxSpent")
local TimeStore = Datastoreservice:GetOrderedDataStore("Time")
local DataStore = Datastoreservice:GetDataStore("Data")
local DonateLeaderboard = workspace.DonateLeaderboard
local DonateGui = DonateLeaderboard.DonateGui
local Donators = DonateGui:GetChildren()
local SpentRobuxLeaderboard = workspace.SpentRobuxLeaderboard
local SpentGui = SpentRobuxLeaderboard.SpentGui
local Spenders = SpentGui:GetChildren()
local TimeLeaderboard = workspace.TimeLeaderboard
local TimeGui = TimeLeaderboard.TimeGui
local MostTime = TimeGui:GetChildren()
local BoardNPC = workspace.BoardNPC
while true do
BoardNPC.Head.BillboardGui.TextLabel.Text = "Loading leaderboards..."
local didwork, DonatedPages = pcall(function()
return DonatedStore:GetSortedAsync(false, 5)
end)
if didwork then
local DonatedPage = DonatedPages:GetCurrentPage()
for rank, data in DonatedPage do
local key = data.key
local value = data.value
local success, PlayerName = pcall(function()
return Players:GetNameFromUserIdAsync(key)
end)
if success then
if rank <= 5 then
local PlayerNameHolder = Donators[rank + 3].NameLabel
PlayerNameHolder.Text = PlayerName
local ValueHolder = Donators[rank + 3].RobuxLabel
ValueHolder.Text = value
end
else
BoardNPC.Head.BillboardGui.TextLabel.Text = "Leaderboards failed to load! Retrying..."
end
end
else
BoardNPC.Head.BillboardGui.TextLabel.Text = "Leaderboards failed to load! Retrying..."
end
local work, RobuxSpentPages = pcall(function()
return RobuxSpentStore:GetSortedAsync(false, 5)
end)
if work then
local RobuxSpentPage = RobuxSpentPages:GetCurrentPage()
for rank, data in RobuxSpentPage do
local key = data.key
local value = data.value
local success, PlayerName = pcall(function()
return Players:GetNameFromUserIdAsync(key)
end)
if success then
if rank <= 5 then
local PlayerNameHolder = Spenders[rank + 3].NameLabel
PlayerNameHolder.Text = PlayerName
local ValueHolder = Spenders[rank + 3].RobuxLabel
ValueHolder.Text = value
end
else
BoardNPC.Head.BillboardGui.TextLabel.Text = "Leaderboards failed to load! Retrying..."
end
end
else
BoardNPC.Head.BillboardGui.TextLabel.Text = "Leaderboards failed to load! Retrying..."
end
local hasworked, TimePages = pcall(function()
return TimeStore:GetSortedAsync(false, 5)
end)
if hasworked then
local TimePage = TimePages:GetCurrentPage()
for rank, data in TimePage do
local key = data.key
local value = data.value
local success, PlayerName = pcall(function()
return Players:GetNameFromUserIdAsync(key)
end)
if success then
if rank <= 5 then
local PlayerNameHolder = MostTime[rank + 3].NameLabel
PlayerNameHolder.Text = PlayerName
local ValueHolder = MostTime[rank + 3].RobuxLabel
ValueHolder.Text = value
end
else
BoardNPC.Head.BillboardGui.TextLabel.Text = "Leaderboards failed to load! Retrying..."
end
end
else
BoardNPC.Head.BillboardGui.TextLabel.Text = "Leaderboards failed to load! Retrying..."
end
for i = 120, 0, -1 do
BoardNPC.Head.BillboardGui.TextLabel.Text = "Leaderboards refresh in " .. i
task.wait(1)
end
for _, Player in Players:GetPlayers() do
local HasTimeData = Player:GetAttribute("HasTimeData")
if HasTimeData then
local success, errormessage = pcall(function()
TimeStore:UpdateAsync(Player.UserId, function(CurrentData)
CurrentData = Player.leaderstats.Time.Value
return CurrentData
end)
end)
if not success then
BoardNPC.Head.BillboardGui.TextLabel.Text = "Leaderboards failed to load! Retrying..."
end
else
local success, errormessage = pcall(function()
TimeStore:SetAsync(Player.UserId, Player.leaderstats.Time.Value)
end)
if not success then
BoardNPC.Head.BillboardGui.TextLabel.Text = "Leaderboards failed to load! Retrying..."
end
end
local HasDonatedData = Player:GetAttribute("HasDonatedData")
if HasDonatedData then
local success, errormessage = pcall(function()
DonatedStore:UpdateAsync(Player.UserId, function(CurrentData)
CurrentData = Player.leaderstats[" Donated"].Value
return CurrentData
end)
end)
if not success then
BoardNPC.Head.BillboardGui.TextLabel.Text = "Leaderboards failed to load! Retrying..."
end
else
local success, errormessage = pcall(function()
DonatedStore:SetAsync(Player.UserId, Player.leaderstats[" Donated"].Value)
end)
if not success then
BoardNPC.Head.BillboardGui.TextLabel.Text = "Leaderboards failed to load! Retrying..."
end
end
local HasRobuxSpentData = Player:GetAttribute("HasRobuxSpentData")
if HasRobuxSpentData then
local success, errormessage = pcall(function()
RobuxSpentStore:UpdateAsync(Player.UserId, function(CurrentData)
CurrentData = Player.leaderstats[" Spent"].Value
return CurrentData
end)
end)
if not success then
BoardNPC.Head.BillboardGui.TextLabel.Text = "Leaderboards failed to load! Retrying..."
end
else
local success, errormessage = pcall(function()
RobuxSpentStore:SetAsync(Player.UserId, Player.leaderstats[" Spent"].Value)
end)
if not success then
BoardNPC.Head.BillboardGui.TextLabel.Text = "Leaderboards failed to load! Retrying..."
end
end
end
end
I technically already have an autosaving system because my “leaderboard refresh system” is not mimicing player’s data it is literally their actual numeric data.
I tried shutting down the server and it didn’t save the data even with the autosaving.
Looks like a lot of work.
This is just my opinion so … I’m not totally in control of the datastore. I can’t change anything about how it works. All I can do is try to cover what I can. If they lose data … they lose data.
Test it … get a game going then do a server shutdown. From what I understand bind to close actually fires a task that can run on it’s own. Giving it time, not related to the game, to run.
Leaving the server or being kicked results in the same behavior. In both cases, you only need to keep track of the players’ User IDs (as keys) and their data (as a dictionary) by referencing them in a variable. Then, save it instantly and repeat the save in case of failure.
Edit: I misunderstood the question, my apologies. It depends on the type of shutdown. It does not always trigger PlayerRemoving or BindToClose , depending on the situation. An update or manual developer shutdown does kick players correctly, however, there are many cases where servers unexpectedly crash, which leads to no signal being sent to these connections.
Anyway, saving players’ data every minute provides benefits without any downsides, so I don’t see a reason why you wouldn’t do it. It’s like an additional 5 lines of code…
This is the problem I’ve always had with bind to close. I think depending on how it happen. They may lose that last save… I don’t how Roblox is doing this it may not matter. I just use bind to close blindly hoping it will work…
You have a lot going on there. IDK but you may want to split that up into a few scripts.
(I am far from a datastore all pro)