I am making a saving system for my game using UpdateAsync. The problem is that when the player leaves the game, PlayerRemoving fires, but UpdateAsync doesn’t.
Here is my code:
local function saveData(player, playerUserId)
if sessionData[playerUserId] then
print("PlayerRemoving fired")
playerData:UpdateAsync(playerUserId, function(oldValue)
local previousData = oldValue.DataId or {DataId = 0}
if sessionData[playerUserId].DataId == previousData.DataId then
sessionData[playerUserId].DataId += 1
print("PlayerData saved")
return sessionData[playerUserId]
else
warn("PlayerData update error")
return nil
end
end)
end
end
playerService.PlayerRemoving:Connect(function(player)
saveData(player,player.UserId)
end)
I tested this script in studio, and when I end the game, it only sometimes prints in the output that the data is saved, but it always prints that PlayerRemoving is fired.
It still doesn’t fire. I used a BindableEvent like you said, but UpdateAsync doesn’t work.
Here is the script:
local function saveData(player, playerUserId)
game.ReplicatedStorage.Events.Saving.SaveSystem:Fire(playerUserId)
end
game.ReplicatedStorage.Events.Saving.SaveSystem.Event:Connect(function(playerUserId)
print("message received")
local success, errorMessage = pcall(function()
playerData:UpdateAsync(playerUserId, function(oldValue)
local previousData
if oldValue["DataId"] == nil then
previousData = {DataId = 0}
else
previousData = oldValue
end
if sessionData[playerUserId].DataId == previousData.DataId then
sessionData[playerUserId].DataId += 1
print("PlayerData saved")
return sessionData[playerUserId]
else
warn("PlayerData update error")
return nil
end
end)
end)
if success then
print("Reset DataStores")
elseif errorMessage then
warn("Outside"..errorMessage)
end
end)
playerService.PlayerRemoving:Connect(function(player)
saveData(player,player.UserId)
end)
The output only prints “message received”, and nothing else.
Instead, You don’t have to use BindableEvents and just use game:BindToClose()
You have 30 seconds to run any code to save everyones data. Here is an example on how you might use it:
local function SaveDataFunction(var1, var2)
--This is where you would run the code to save your data...
end
game:BindToClose(function()
--if not RunService:IsStudio() then
for i,v in game.Players:GetPlayers() do
coroutine.wrap(SaveDataFunction)(var1, var2) --You will need to change this line to the real function and variables, but keeping the same format and using the Coroutine.
end
--end
end)
This would work but game:BindToClose() only runs during a shutdown or when the last player in a server is leaving. What about when a player is leaving a server that still has other players in it?
You use both PlayerRemoving and BindToClose together (Sometimes Player removing isn’t called when BindToClose is called which equals dataloss). You can add a debounce to prevent both saving at the same time and advance the set up I made above. I provided a basic example on how to use BindToClose but you would still keep PlayerRemoving, and maybe advance it a little bit more to how your using it in this case
You can’t… that’s why you use BindToClose… it’s kinda the whole purpose of BindToClose and why we use it in datastores, as if PlayerRemoving isn’t fired, that’s usually because the server was forced to be shutdown… (as the player didn’t leave normally, they were forced to leave the server (Developers shutting down the server, Roblox server needs to shutdown from roblox, whatever it may be, the server is closing, and therefore PlayerRemoving isn’t fired))so we get all the players within the server if BindToClose is fired)
Okay, but if PlayerRemoving fails to run and the player is not forced to leave the server and left normally (meaning :BindToClose() doesn’t function), then the player would lose their data. How would I go about preventing this?
What you kinda asked to me in a rephrased way is:
How would a Player leave the server without them leaving?
PlayerRemoving will fire if the player is leaving… (they open the menu and leave the game, or forced kicked normally by using :Kick() function)
:BindToClose() will/can prevent PlayerRemoving from firing if its unexpectedly shutting down. This is because :BindToClose Is fired first while… → Server Shut downs so all scripts with a RBXConnection will no longer detect the event → All players leave so PlayerRemoving is fired, but scripts can’t detect it. → Dataloss.
But this isn’t an issue with normal leaving at all. I guess it would fire yes, but your scripts won’t read the event because they are shutting down
Yes Kinda. You have 30 seconds to run any code in your game after BindToClose. Because :SetAsync() and :UpdateAsync() Yield, we call the corountine.wrap so everyone’s data is getting saved at the same time, instead of waiting for one’s data to save… Then call next person in loop and wait for their data to save etc. it’s an important step in minimizing data loss, and while you could call it normally, it won’t break it per say, but if the server has a lot of people, some peoples data might not be saved. And for adding 2 extra words, it’s kinda worth to do it anyways.
I tried the solution you gave me in studio, and everything runs fine except for the fact that when I tested the game multiple times in studio, :UpdateAsync() only sometimes fires to save data.
Here is the new script:
local function setupPlayerData(player)
local playerUserId = player.UserId
local data
local success, response
local count = 0
repeat
if count >= 1 then
warn("Retrying, count:", count, "\nError:", response)
wait(0.5)
else
warn("Trying, count:", count, "\nError:", response)
end
success, response = pcall(function()
data = playerData:GetAsync(playerUserId)
end)
count = count + 1
until success
if success then
warn("SUCCESS!!")
if data then
sessionData[playerUserId] = data
else
sessionData[playerUserId] = dataTemplate
sessionData[playerUserId].Nickname = player.DisplayName
end
else
warn("NO SUCCESS :(")
end
end
local function saveData(player, playerUserId)
print("message receive")
print(sessionData[player.UserId])
local success, errorMessage = pcall(function()
playerData:UpdateAsync(playerUserId, function(oldValue)
print(oldValue)
local previousData = oldValue or {DataId = 0}
if sessionData[playerUserId].DataId == previousData.DataId then
sessionData[playerUserId].DataId += 1
print("PlayerData saved")
return sessionData[playerUserId]
else
warn("PlayerData update error")
return nil
end
end)
end)
if success then
print("Reset DataStores")
elseif errorMessage then
warn(errorMessage)
end
end
playerService.PlayerRemoving:Connect(function(player)
if player.Miscellaneous.AlreadySavedData.Value == false then
player.Miscellaneous.AlreadySavedData.Value = true
saveData(player,player.UserId)
end
end)
game:BindToClose(function()
for i, player in pairs(game.Players:GetPlayers()) do
if player.Miscellaneous.AlreadySavedData.Value == false then
player.Miscellaneous.AlreadySavedData.Value = true
coroutine.wrap(saveData)(player,player.UserId)
end
end
end)
It still doesn’t work even though I removed the pcall function. Here is the saveData function:
local function saveData(player, playerUserId)
playerData:UpdateAsync(playerUserId, function(oldValue)
print(sessionData[player.UserId], oldValue)
local previousData = oldValue or {DataId = 0}
if sessionData[playerUserId].DataId == previousData.DataId then
sessionData[playerUserId].DataId += 1
print("PlayerData saved")
return sessionData[playerUserId]
else
warn("PlayerData error")
return nil
end
end)
end
:UpdateAsync() Is not meant for preventing Data loss, more so, the cause of :UpdateAsync() and why it Exists is to Compare Old data, before rewriting new data.
If anything UpdateAsync() is worse then SetAsync() when comparing for Dataloss, in the fact that UpdateAsync() in simple terms, Has to read the data like :GetAsync, and Save the data like :SetAsync(), and counts against both the read and write limit. The Benefit of Update Async is when dealing with 2 different servers changing the Same data within that key at the same time, UpdateAsync reads the current key value before updating it.
Now you are Comparing Data so your usage is correct. It won’t really help or prevent dataloss if that is the benefit your looking for. (To be clear, Both :GetAsync and :SetAsync are both capable and great for what they do. But none provide an ‘extra protection’ aganist dataloss, and its way better if you can provide pcalls, and make sure you handling data correctly etc. )
So how would I go about ensuring that UpdateAsync always functions when the player leaves? Or maybe it’s because I’m testing it in studio and not in-game?
I don’t see how you’re losing data with SetAsync. Unless you’re running this while your editing in the studio … that can mess up with anyway of doing it. AS you quit testing at odd times.
This is why I also use …
local rs = game:GetService("RunService")
if not rs:IsStudio() then
-- your code ...
end
This way I can test the rest of my program without killing my saved data, by exiting my test to soon.