Help me BindToClose

P.S Problem with BindToClose solved, but there is no problem with saving data

Briefly about the problem: I have my own script for saving data, I have a function that saves them when the player exits. It is already clear that the function does not work.

When a player logs out with him, the server shuts down, so I used BindToClose to stop the server from shutting down. Data is not saved, Studio freezes and gives an error (Not running script because past shutdown deadline).

I read a lot on the forum, there were a lot of articles with no solution or no suitable solution. But I found the answer…

This answer had a solution that works. As you understand, the answer does not suit me

Of course, I did not understand what kind of code was written, but I understood:

  1. The code works

  2. The code is complex

  3. Code works with threads

  4. The code doesn’t do what I want

By the way, this code:
aloe.FailSafeSave = function()
	-- this is only called when the game is shutting down
	local mainThread = coroutine.running() -- This just gets a reference to the current thread so we can yield it and resume it later
	local numThreadsRunning = 0

	for _, player in ipairs(game.Players:GetPlayers()) do
		local startSavingThread = coroutine.wrap(function()
			aloe.Save(player) -- Async function, yields until saving finishes (could implement a retry here if you pcall it and check for failure)
			numThreadsRunning -= 1 -- This thread finished, decrement the counter

			if numThreadsRunning == 0 then
				-- Resume the main thread if this was the last thread to run
				coroutine.resume(mainThread)
			end
		end)

		numThreadsRunning += 1
		
		-- No need to defer, coroutine.wrap() already yields when it starts,
		-- so numThreadsRunning will have a chance to count all the way up before this actually runs
		startSavingThread(player) -- Start the saving thread
	end

	if numThreadsRunning > 0 then
		-- Gets resumed once all saveThreads have finished
		coroutine.yield()
	end
end

aloe.Save = function(player)
	dataSet:SetAsync(player.UserId, sessionData[player])
	RapSet:SetAsync(player.Name .. ";" .. player.UserId, aloe.RAPCalculate(player))
end

As I understand it, when the server is turned off, the code calls the save function and creates threads, but I need to wait until the already saving function saves everything, then you can turn off the server

I would be grateful if you could explain to me how I can change the save function or write the code for BindToClose, and perhaps adapt the code above. Or to understand what I do not understand in the logic of closing or saving?

Part of the code needed to save data (and needed to understand the problem, without unnecessary code)

local PlayerData = {}
local numDataForSave = 0

function PlayerAdded (plr)
	
	local UserId = plr.UserId 
	local success, Key = pcall(function ()
		return Playerid(UserId)
	end)
	
	if not success then WriteErrors(Key, nil, UserId) Key = UserId end
	
	PlayerData[Key] = {}
	numDataForSave += 1
	
	while wait() do
		if Players.PlayerRemoving:Wait().UserId == UserId then
			for i, v in pairs(PlayerData[Key]) do -- [key] = {[DSS] = {["Data"] = nil, ["userIds"] = UserId, ["options"] = options, ["Edit"] = false}}
				if v["Edit"] then
					
					local key = i
					local dssName = v
					local data = v["Data"]
					local userIds = v["userIds"]
					local options = v["options"]
						
					for x = 1, 5 do
						local PcallError = nil
						
						if data == nil then
							local success, Error = pcall(function()
								local GDS = dss:GetDataStore(dssName)
								GDS:RemoveAsync(key)
							end)
							PcallError = Error
						else
							local success, Error = pcall(function()
								local GDS = dss:GetDataStore(dssName)
								GDS:SetAsync(key, data, userIds, options)
							end)
							PcallError = Error
						end
						
						
						if success == false then	
							WriteErrors("An error occurred while recording: " .. PcallError, dssName, key) 
						else
							break
						end
						
						task.wait(0.1)
					end
					
					--PlayerData[key][dssName] = nil
					
				end
				
			end
			PlayerData[Key] = nil
			numDataForSave -= 1
			break
		end
	end
	
end

if AutoSave then
	Players.PlayerAdded:Connect(PlayerAdded)
	
	game:BindToClose(function() -- I tried many solutions with for and others with while
		while numDataForSave > 0 do
			print(numDataForSave)
			task.wait(1)
		end
	end)
end

New working code:

I’ve usually just implemented a wait statement in the bind to close, usually for like 5 seconds. This should give time to any player removing listeners that are still attempting to save the leaving player’s data.

1 Like

Yes, I also thought to do this, but you can not predict how long it will take to save. May not have time to save if the DataStore fails to save the first time. I also want to improve my luau skills

1 Like

You need to save with BindToClose on studio, as you said you can’t predict how much will it take DataStores to save the data. But in here another problem comes in, you will get a warning saying “DataStore request was added to queue”, in order to fix this you need to disable the PlayerRemoving event for studio only. In this situation, RunService:IsStudio is what you are looking for.

What in the…

NO?!
I’ll be back. I’ll fix this up for you real quick.

2 Likes

Here. Don’t use such a busy system for something you clearly KNEW was an event… I’m so confused why you did it that way when there’s not really any benefit.

local PlayerData = {}
local numDataForSave = 0

function PlayerAdded (plr)
	local UserId = plr.UserId 
	local success, Key = pcall(function ()
		return Playerid(UserId)
	end)

	if not success then WriteErrors(Key, nil, UserId) Key = UserId end

	PlayerData[Key] = {}
	numDataForSave += 1
end

function PlayerRemoving (plr)
	local UserId = plr.UserId 
	local success, Key = pcall(function ()
		return Playerid(UserId)
	end)

	if not success then WriteErrors(Key, nil, UserId) Key = UserId end
	if not PlayerData[Key] then return end -- If we have no player data, then we return.

	for i, v in pairs(PlayerData[Key]) do -- [key] = {[DSS] = {["Data"] = nil, ["userIds"] = UserId, ["options"] = options, ["Edit"] = false}}
		if v["Edit"] then

			local key = i
			local dssName = v
			local data = v["Data"]
			local userIds = v["userIds"]
			local options = v["options"]

			for x = 1, 5 do
				local PcallError = nil

				if data == nil then
					local success, Error = pcall(function()
						local GDS = dss:GetDataStore(dssName)
						GDS:RemoveAsync(key)
					end)
					PcallError = Error
				else
					local success, Error = pcall(function()
						local GDS = dss:GetDataStore(dssName)
						GDS:SetAsync(key, data, userIds, options)
					end)
					PcallError = Error
				end


				if success == false then	
					WriteErrors("An error occurred while recording: " .. PcallError, dssName, key) 
				else
					break
				end

				task.wait(0.1)
			end

			PlayerData[key][dssName] = nil
		end
	end

	PlayerData[Key] = nil
	numDataForSave -= 1
end

if AutoSave then
	Players.PlayerAdded:Connect(PlayerAdded)
	Players.PlayerRemoving:Connect(PlayerRemoving)
	game:BindToClose(function() -- I tried many solutions with for and others with while
		local Timer = 0
		while numDataForSave > 0 and Timer < 20 do -- if it doesn't finish in 20, it won't finish in 30.
			print(numDataForSave)
			task.wait(1)
			Timer += 1
		end
		warn(("Failed to save %i data entries."):format(numDataForSave)) -- viewable in studio for debugging.
		return true;
	end)
end
1 Like

Thanks, now the BindToClose function works fine and the save function looks better, but the DataStore does not want to save :frowning: The data is simply not written, although it reaches the save code and does not cause an error

I tested in the game it also works and the code does not give an error that the requests are full

By the way, how did I create this code. I was thinking about how to solve the problem with saving and the idea came to my mind to take the concept from the old code. Moreover, I considered this idea a good one, knowing that I then wrote very bad code that made me want to cry.

Carefully, this is very bad code.
local Settings = game:GetService("DataStoreService"):GetDataStore("Settings")
local SettingsEvent = game:GetService("ReplicatedStorage"):WaitForChild("Settings")
local AnswerSettings = game:GetService("ReplicatedStorage"):WaitForChild("BnswerSettings")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DataStoreFun = ReplicatedStorage:WaitForChild("DataStore")
SettingsEvent.OnServerEvent:Connect(function(Player, n, N, M, b)
	local metaTable = {}
	local SettingsData = Settings:GetAsync(Player.UserId)
	if N == 1 then
		local r = SettingsData.Sound
		local j = SettingsData.Timer
		local y = SettingsData.MinTime
		AnswerSettings:FireClient(Player, r, j, y)
	elseif N == 2 then
		if M == 1 then
			metaTable.Sound = false
			metaTable.Timer = SettingsData.Timer
			metaTable.MinTime = SettingsData.MinTime
			metaTable.MaxTime = SettingsData.MaxTime
			local DataStoreData = DataStoreFun:Invoke("Settings", metaTable, Player.UserId, {})
			print("Sound = false")
		else
			metaTable.Sound = true
			metaTable.Timer = SettingsData.Timer
			metaTable.MinTime = SettingsData.MinTime
			metaTable.MaxTime = SettingsData.MaxTime
			print("Sound = true")
			local DataStoreData = DataStoreFun:Invoke("Settings", metaTable, Player.UserId, {})
		end
	elseif N == 3 then
		if M == 1 then
			print("Timer = false")
			metaTable.Sound = SettingsData.Sound
			metaTable.Timer = false
			metaTable.MinTime = SettingsData.MinTime
			metaTable.MaxTime = SettingsData.MaxTime
			local DataStoreData = DataStoreFun:Invoke("Settings", metaTable, Player.UserId, {})
		elseif M== 2 then
			if string.len(b) < 4 then
				print("Timer = true")
				metaTable.Sound = SettingsData.Sound
				metaTable.Timer = SettingsData.Timer
				metaTable.MinTime = b
				metaTable.MaxTime = b
				local DataStoreData = DataStoreFun:Invoke("Settings", metaTable, Player.UserId, {})
			end
		end
	end
end)
game.Players.PlayerAdded:Connect(function(Player)
	local SettingsData = Settings:GetAsync(Player.UserId)
	if SettingsData.Timer == true then
		Player.CharacterAdded:Wait()
		local Time = os.time()
		game.Players.PlayerRemoving:Connect(function(pLR)
			if pLR == Player then
				local d = os.time() - Time
				print(d)
				d = d / 60
				print(d)
				local d = math.modf(d)
				print(d)
				d = SettingsData.MinTime - d
				if d >=0 then
					print(d)
					SettingsData.MinTime = d
					local DataStoreData = DataStoreFun:Invoke("Settings", SettingsData, Player.UserId, {})
				else
					print("F")
					SettingsData.MinTime = 0
					local DataStoreData = DataStoreFun:Invoke("Settings", SettingsData, Player.UserId, {})
				end
			end
		end)
	end
end)

I want to say I don’t do that anymore.