Hello everyone, I was testing my game when all my data was reset. It could have been something to do with being on super slow wifi (I was connected to a really bad hotspot), or it could have been a coincidence. Either way, I can’t figure out why this happened.
Note: This only happened to my main account. My alt account’s progress which I have just tested worked fine.
The datastore code is as follows:
-- // Assigning variables //
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("MyDataStore") -- This can be changed to whatever you want
local function saveData(player) -- The functions that saves data
local tableToSave = {
player.leaderstats.GoldCoins.Value; -- First value from the table
player.BoughtFiveHourBook.Value; -- Second value from the table
player.FiveHourBookBoostLeft.Value;
player.leaderstats.Highscore.Value,
player.Experience.Value;
}
local success, err = pcall(function()
dataStore:SetAsync(player.UserId, tableToSave) -- Save the data with the player UserId, and the table we wanna save
end)
if success then -- If the data has been saved
----print("Data has been saved!")
else -- Else if the save failed
----print("Data hasn't been saved!")
warn(err)
end
end
game.Players.PlayerAdded:Connect(function(player) -- When a player joins the game
-- // Assigning player stats //
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
local GoldCoins = Instance.new("IntValue")
GoldCoins.Name = "GoldCoins"
GoldCoins.Parent = leaderstats
local FiveHourBookBoostLeft = Instance.new("IntValue")
FiveHourBookBoostLeft.Name = "FiveHourBookBoostLeft"
FiveHourBookBoostLeft.Parent = leaderstats.Parent
local BoughtFiveHourBook = Instance.new("BoolValue")
BoughtFiveHourBook.Name = "BoughtFiveHourBook"
BoughtFiveHourBook.Parent = leaderstats.Parent
local Score = Instance.new("IntValue")
Score.Name = "Highscore"
Score.Parent = leaderstats
local XP = Instance.new("IntValue")
XP.Name = "Experience"
XP.Parent = player
local data -- We will define the data here so we can use it later, this data is the table we saved
local success, err = pcall(function()
data = dataStore:GetAsync(player.UserId) -- Get the data from the datastore
end)
if success and data then -- If there were no errors and player loaded the data
GoldCoins.Value = data[1] -- Set the GoldCoins to the first value of the table (data)
BoughtFiveHourBook.Value = data[2] -- Set the coins to the second value of the table (data)
FiveHourBookBoostLeft.Value = data[3]
Score.Value = data[4]
XP.Value = data[5]
else -- The player didn't load in the data, and probably is a new player
----print("The player has no data!") -- The default will be set to 0
end
end)
game.Players.PlayerRemoving:Connect(function(player) -- When a player leaves
local success, err = pcall(function()
saveData(player) -- Save the data
end)
if success then
----print("Data has been saved")
else
----print("Data has not been saved!")
end
end)
game:BindToClose(function() -- When the server shuts down
for _, player in pairs(game.Players:GetPlayers()) do -- Loop through all the players
local success, err = pcall(function()
saveData(player) -- Save the data
end)
if success then
----print("Data has been saved")
else
----print("Data has not been saved!")
end
end
end)
Also, I have not recently changed anything in the datastore script.
Never use DataStoreService, data loss happen all the time with it. No popular game or competent developer use DataStoreService, instead there are better alternatives.
The one I use, and my favorite one is ProfileService. By using this you can rest assured that you will never encounter any data loss ever again. They have a full documentation if you’re interested.
Not just how you use it, it would also depend on if the Roblox servers are slow or if Roblox is down, then you don’t really have a way to prevent data loss.
Been hammering the datastore for years and have never had a real problem with data errors.
Ran into a few over sends, but cleaned up my code and learned how to save that data when they exit only. While working off locals in game and not using datastore read/write constantly.
ProfileService also just uses datastores; they just have extra stuff like session locking and backups (which by the way, are now supported directly by datastores). Roblox datastores are reliable if you use them and handle errors correctly.
Thanks. So, would retrying if the get/set fails be something like this?
game.Players.PlayerRemoving:Connect(function(player) -- When a player leaves
local success = nil
while not success do
wait()
local success, err = pcall(function()
saveData(player) -- Save the data
end)
if success then
----print("Data has been saved")
else
end
end
end)
Also, is the reason that the datastore was completely wiped because I was using SetAsync instead of UpdateAsync? It makes sense to me but I just want to make sure. (If I used UpdateAsync instead, the player would only lose the data from their playing session, correct?)
It’s possible, since the initial attempt to load the data might’ve failed. It might’ve overwrote the data in the datastore with the default data because it didn’t check if it failed to load the data in the 1st place.
The data loss didn’t happen because you don’t retry or use SetAsync (popular myth).
It’s because you don’t check if the data was loaded properly so sometimes your script is saving data which didn’t load (default).
To prevent this, after making sure data has loaded properly, add loaded player to a table.
On player removing, check if the player is in this table and if they are, save the data.
How would I make sure that the data actually didn’t load, and the player is not new? (It would look the same in both instances)
I know I could use a datastore to keep track of if a player has joined before, but that could also fail. I am thinking of using a badge to check if it’s their first time playing, but player can delete badges from their inventory. So, how would I do this?
Personally I disagree, I think there are many well coded games that uses the default data store and generally in the event of data loss, it is typically a small issue in the code.
New player: data == nil
Not loaded properly: success == false or (data and typeof(data) ~= 'table')
“not loaded properly” condition will rarely evaluate to true, most of the time it’s long request time causing your script to save the data before it loads and that’s why it’s important to save the data only when you’re sure it had loaded.
Anyway, it’s always better to protect yourself from such scenario as well.