PlayerRemoving and BindToClose DataStore Redundancy?

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)
4 Likes

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.

2 Likes

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)
4 Likes

Doesn’t seem to work every time. Thanks for trying though.

3 Likes

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.

3 Likes

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.

1 Like

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
2 Likes

I ended up going with the following code:

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.

2 Likes

Yikes.

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.

9 Likes

Does kicking players save the player data? And does shutting down without this script not kick the players anyway?

1 Like

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.

4 Likes