Hi, so I made the following code to save players’ cash when they leave the game, and also in-case the server shutsdown. PlayerRemoving will not fire without BindToClose in the event of a game shutdown, which makes BindToClose necessary, but then I’m stuck with the same save occurring twice.
Any advice for how I could remove this redundancy so that it doesn’t cause DataStore queues and such? I know I could make BindToClose nothing more than the maximum 30 second delay, but that seems a bit patchwork to me. Thanks, I appreciate any suggestions.
game.Players.PlayerRemoving:Connect(function(player)
if player.SafeToSave.Value then
local CashData = player.leaderstats.Cash.Value
local TotalCashData = player.leaderstats["Total Cash"].Value
local Success, Result = pcall(function()
CashDataStore:SetAsync(tostring(player.UserId), CashData)
TotalCashDataStore:SetAsync(tostring(player.UserId), TotalCashData)
end)
if not Success then
warn(Result)
end
print('Data Saved on Removal?')
end
end)
game:BindToClose(function()
--if not RunService:IsStudio() then
local players = Players:GetPlayers()
for _, player in pairs(players) do
if player.SafeToSave.Value then
local CashData = player.leaderstats.Cash.Value
local TotalCashData = player.leaderstats["Total Cash"].Value
local Success, Result = pcall(function()
CashDataStore:SetAsync(tostring(player.UserId), CashData)
TotalCashDataStore:SetAsync(tostring(player.UserId), TotalCashData)
end)
if not Success then
warn(Result)
end
print('Data Saved on Shutdown?')
end
end
--end
end)
If both work, you would only need to use one on the game shutdown?
You could store a variable to tell if the game has shutdown, or a table to tell if the player’s data has already been saved/is saving.
Maybe you could try disconnecting the original Players.PlayerRemoving event on close, and then manually save data for each player:
local function PlayerSaveFunction(player)
--fn here
end
local connection = game.Players.PlayerRemoving:Connect(PlayerSaveFunction)
game:BindToClose(function()
connection:Disconnect()
for _, player in ipairs(game.Players:GetPlayers()) do
PlayerSaveFunction(player)
end
end)
Why not rather than calling save, you call a ‘clear’ function which saves the players data and then removes their data from the cache, and in the saving function if their data is not in the cash it doesn’t attempt to save?
If it is programmed right you shouldn’t need to worry about anything going wrong here.
Here’s a good example:
local clearing={}
function database.clear(key)
if cache[key] then
local success=save(key)
if success then
cache[key]=nil
else
clearing[#clearing+1]=key
end
return success
end
end
function database.manualsave(key)
return save(key)
end
The table ‘clearing’ can be used to attempt to re-save players data if it did not successfully save the first time.
Here’s a good example of the code used to attempt to save players data in the event that the initial save failed:
local saveinterval =30
local nextupdate =tick()+saveinterval
game:GetService("RunService").Heartbeat:connect(function()
if nextupdate<tick() then
print("Data saved")
nextupdate=nextupdate+saveinterval
local ppl=game.Players:GetChildren()
for i=1,#ppl do
save(ppl[i].userId)
end
--Basically, if datastore goes down, and somebody's data wasn't able to save, this would ensure that people's data would save.
-- In other words, it is very important.... Sort of.
for i = 1,#clearing do
wait()
local saved=database.clear(clearing[i])
if saved then
table.remove(clearing,i)
else
i=i+1
end
end
end
end)
I can link a full ‘database’ module I typically use for this sort of stuff if necessary.
Another rather simple thing to do would be to put a BoolValue inside of the player in the event that the BindToClose function already saved their data, and look for that in the .playerRemoving function before attempting to save again.
If interested, here is my system for saving data, but keep in mind this will require some outside programming for organizing the data properly:
This also comes with a module called ‘Network’ which always seems to come in handy, so here is the other end that is required to make the ‘Network’ module work properly
To make all of this work, put the ‘Server Modules’ folder in ServerStorage and name it Modules
The following code should simplify some things with this. Please note that the functions present here aren’t required, and any sort of table modifying function will work with this. This database is also built with encryption as well as the ability to spread a save along multiple datastores in the event that the save is too large for one datastore.
This code will need to be a Server Script in ServerScriptService.
local Modules = game.ServerStorage.Modules
local network = require(Modules.Network)
local database = require(Modules.Database)
local playerdata ={}
local function getdata(player)
local data=playerdata[player]
if not data then
data=database.load(player.userId,false)
--warn('Test2:',data.stats.money)
playerdata[player]=data
wait()
print('PlayerDataLoaded')
network:send(player,"loadplayerdata",data)
wait(2.5)
--network:send(player,"updatemoney",data.stats.money) -- XD remember credits?
network:send(player,"updatescrap",data.stats.scrap)
network:send(player,"updateexperience",data.stats.experience)
--warn('Test7',playerdata[player].stats.money)
end
return data
end
--Example:
--DataStructure: {Stats={Money=0}}
--Code: updateplayerdata(player,100,"Stats","Money")
--Outcome: {Stats={Money=100}}
local function updateplayerdata(player,value,...)
local keys={...}
local data=getdata(player)
for i=1,#keys-1 do
if not data[keys[i]] then
data[keys[i]]={}
end
data=data[keys[i]]
end
data[keys[#keys]]=value
end
game:BindToClose(function()
if not RunService:IsStudio() then
local players = Players:GetPlayers()
for _, player in pairs(players) do
player:Kick("Server is shutting down: Please rejoin, your data will be saved.")
end
while true do
wait()
end
else
wait(5)
end
end)
While it isn’t exactly what I was looking for, it does hopefully give the server an easy enough time dealing with a shutdown.
Why not cache player data in a ModuleScript or in ServerStorage and use leaderstats instead for only displaying data? That way when player objects are destroyed, you don’t have to keep strong references to players and suffer either a memory leak or a server staying up until the maximum time frame has been hit.
You can also turn your save code into a function that gets called in either case, when the player leaves or when the server shuts down. When the player leaves, their data is saved and removed from the cache. When the server closes, the code runs through the cache, saves all cached data and then allows the server to close.
On another note, you’re probably going to experience some serious throttling issues if you keep your DataStore saving method the way it is. You’re spending two calls of the budget for a single player whereas you can just save their data to a single DataStore in a table. That’d make this all for naught.
Basically. The code that was written in that post is a pointless operation.
BindToClose is fired when the server is closing, so kicking all the players wouldn’t do anything. If data is stored in the player object, that may also be pretty bad. Kicking players without a server closure would save their data if that function exists and is connected to PlayerRemoving though.
Typically for BindToClose, I would just run through my data cache, save data and then clear that item out of the cache. Once that happens, then I just let the server close.