Can this happen for UpdateAsync?

I have come across a weird thing.

But here a small look on the saving system:

local function Saving(Plr,eInfo)
	
	local DataSave = {}


	for _, v in ipairs(Plr.Stats:GetChildren()) do
		DataSave[v.Name] = v.Value
	end

	DataSave.RedeemedCodes = RedeemedCodesCache[Plr.UserId] or {}
	
	local dailyFolder = Plr:FindFirstChild("DailyQuests")

	if dailyFolder then
		DataSave.DailyProgress = {}
		DataSave.DailyCompleted = {}

		local progressFolder = dailyFolder:FindFirstChild("Progress")
		local completedFolder = dailyFolder:FindFirstChild("Completed")

		DataSave.DailyQuestList = {}

		local progressFolder = dailyFolder:FindFirstChild("Progress")
		if progressFolder then
			for _, v in ipairs(progressFolder:GetChildren()) do
				table.insert(DataSave.DailyQuestList, v.Name)
			end
		end
		
		if progressFolder then
			for _, v in ipairs(progressFolder:GetChildren()) do
				DataSave.DailyProgress[v.Name] = v.Value
			end
		end

		if completedFolder then
			for _, v in ipairs(completedFolder:GetChildren()) do
				DataSave.DailyCompleted[v.Name] = v.Value
			end
		end

		DataSave.LastDailySeed = math.floor(os.time() / 86400)
	end
	
	local weeklyFolder = Plr:FindFirstChild("Weekly")

	if weeklyFolder then

		DataSave.WeeklyCompleted = {}

		for _,v in pairs(weeklyFolder:GetChildren()) do
			if v:IsA("BoolValue") then
				DataSave.WeeklyCompleted[v.Name] = v.Value
			end
		end

		DataSave.LastWeeklySeed = weeklyFolder.Seed.Value
	end
	
	DataSave.Achievements = {}

	for _, achievement in ipairs(Achievements) do
	--	print(achievement.Completed)
		DataSave.Achievements[achievement.Id] = achievement.Completed
	end
	
	print("ABOUT TO SAVE", eInfo)
	
	print(
		"UpdateAsync budget:",
		game:GetService("DataStoreService"):GetRequestBudgetForRequestType(
			Enum.DataStoreRequestType.UpdateAsync
		)
	)
	
	local success, err = pcall(function()
		print("PCALL ~")
		PowerDataStore:UpdateAsync(Plr.UserId, function(oldData)
			print("Inside of the PowerDatastore")
		--	oldData = oldData or {}

		--	for k, v in pairs(DataSave) do
		--		oldData[k] = v
		--	end
			return DataSave
		end)
	end)
	
	print("PCALL FINISHED", success, err)

	if not success then
		warn("Failed to save data for", Plr.Name, err)
	else
		print("SUCCESsy")
	end
end
local STOP = false
game.Players.PlayerRemoving:Connect(function(Plr)
	if STOP == true then return end
	Saving(Plr,"REMOVE")
end)

game:BindToClose(function()
	STOP = true
	for __, Plr in pairs(game.Players:GetPlayers()) do
		--task.wait(2.5)
		Saving(Plr,"CLOSETHEBINGIND")
	end
	task.wait(5)
end)

task.spawn(function()
	while task.wait(350) do
		--if STOP then break end
		pcall(function()
			for _, Plr in pairs(game.Players:GetPlayers()) do
				--task.wait(2)
				Saving(Plr)
			end
		end)
	end
end)

Now the issue was that SOMETIMES, not always, it does not save. This would occur when only printing PCALL but not inside Powerdatastore or anything after, and even if it saved it would not print successy. Only after adding task.wait(6) in the bind to close function it printed these things and the content would be saved.

I have asked my local AI companion, which parents I highly dislike, and they gave me this as I asked them if it was possible if the player left before bindtoclose, if it could break. And it seems like that is somewhat the case, but there are some issues with this, like should bindtoclose not wait, so that all players leave and then run. which would make calling the function in the first place impossible (It is possible though). And flaw, flaw, flaw. Please explain I need a good answer.

2 Likes

well, your implementation of PowerDataStore:UpdateAsync(Plr.UserId, function(oldData) is not good, but seems you do not actually need it, SetAsync would work too, well, because, I presume, one player can’t leave more than one server of the game at the same time (I am happy to be wrong, please correct me in this case).

So my guess is that you are observing this while testing in studio. And it has been noticed before, for example: Data store is not saving because of the server fast shutdown
This most likely won’t happen in published games.

I think game:BindToClose() doesn’t wait for all players to leave. PlayerRemoving is called before the player is actually removed from the Players list, but inside Saving() you have UpdateAsync(), which is a yield operation. Therefore, game.Players.PlayerRemoving can start saving and stop inside UpdateAsync, while the rest of the server’s shutdown logic continues to execute. In result, by the time BindToClose is executed, the player may already be removed from the Players list, so:

game.Players:GetPlayers()

returns {}. This means that BindToClose itself works fine; it’s just that your for statement no longer finds the player. Therefore, I wouldn’t rely on Players:GetPlayers() inside BindToClose as the only way to determine which players to save. Moreover, you have another problem:

local STOP = false

I think there’s a possible case where the value of this variable changes to true and the save cycle isn’t fully executed. So I don’t think task.wait(6) makes any significant difference. It’s quite possible it simply visually indicates that the data has been saved, even though it hasn’t.It’s better to have the system explicitly track unfinished saves and wait for them to complete in BindToClose, rather than simply waiting a fixed 6 seconds.

I am not quite sure what you mean I do not use it good, but if it is because the olddata variable is not used, it was because I was testing. Means I am now using it again.

Then again this error that sometimes players were losing data was a problem when we tested it in Roblox too.

Yes that is what I wanted to say that GetPlayers() is empty once BindToClose happens (Sometimes). Thats why the wait(6) helped. Because it waited for the UpdateAsync to load. Of course I tried to change it too, to this. Where I am using a table. Would you say this is better? I have not had any issues with data loses again yet.

local SavingPlayersForBinding = {}
local function Saving(Plr,eInfo)
	if not SavingPlayersForBinding[Plr.UserId] then SavingPlayersForBinding[Plr.UserId] = true end
	local DataSave = {}


	for _, v in ipairs(Plr.Stats:GetChildren()) do
		DataSave[v.Name] = v.Value
	end

	DataSave.RedeemedCodes = RedeemedCodesCache[Plr.UserId] or {}
	
	local dailyFolder = Plr:FindFirstChild("DailyQuests")

	if dailyFolder then
		DataSave.DailyProgress = {}
		DataSave.DailyCompleted = {}

		local progressFolder = dailyFolder:FindFirstChild("Progress")
		local completedFolder = dailyFolder:FindFirstChild("Completed")

		DataSave.DailyQuestList = {}

		local progressFolder = dailyFolder:FindFirstChild("Progress")
		if progressFolder then
			for _, v in ipairs(progressFolder:GetChildren()) do
				table.insert(DataSave.DailyQuestList, v.Name)
			end
		end
		
		if progressFolder then
			for _, v in ipairs(progressFolder:GetChildren()) do
				DataSave.DailyProgress[v.Name] = v.Value
			end
		end

		if completedFolder then
			for _, v in ipairs(completedFolder:GetChildren()) do
				DataSave.DailyCompleted[v.Name] = v.Value
			end
		end

		DataSave.LastDailySeed = math.floor(os.time() / 86400)
	end
	
	local weeklyFolder = Plr:FindFirstChild("Weekly")

	if weeklyFolder then

		DataSave.WeeklyCompleted = {}

		for _,v in pairs(weeklyFolder:GetChildren()) do
			if v:IsA("BoolValue") then
				DataSave.WeeklyCompleted[v.Name] = v.Value
			end
		end

		DataSave.LastWeeklySeed = weeklyFolder.Seed.Value
	end
	
	DataSave.Achievements = {}

	for _, achievement in ipairs(Achievements) do
	--	print(achievement.Completed)
		DataSave.Achievements[achievement.Id] = achievement.Completed
	end
	
	print("ABOUT TO SAVE", eInfo)
	
	print(
		"UpdateAsync budget:",
		game:GetService("DataStoreService"):GetRequestBudgetForRequestType(
			Enum.DataStoreRequestType.UpdateAsync
		)
	)
	
	local success, err = pcall(function()
		print("PCALL ENTER ME AH~")
		PowerDataStore:UpdateAsync(Plr.UserId, function(oldData)
			print("Insides of the PowerDatastore")
			oldData = oldData or {}

			for k, v in pairs(DataSave) do
				oldData[k] = v
			end
			return oldData
		end)
	end)
	
	print("PCALL FINISHED", success, err)

	if not success then
		warn("Failed to save data for", Plr.Name, err)
	else
		print("SUCCESsy")
	end
	
	if SavingPlayersForBinding[Plr.UserId] then SavingPlayersForBinding[Plr.UserId] = nil end
end
local STOP = false
game.Players.PlayerRemoving:Connect(function(Plr)
	if STOP == true then return end
	Saving(Plr,"REMOVE")
end)

game:BindToClose(function()
	STOP = true
	for __, Plr in pairs(game.Players:GetPlayers()) do
		--task.wait(2.5)
		Saving(Plr,"CLOSETHEBINGIND")
	end
	--task.wait(5)
	repeat task.wait(0.25) until next(SavingPlayersForBinding) == nil
end)

task.spawn(function()
	while task.wait(350) do
		--if STOP then break end
		pcall(function()
			for _, Plr in pairs(game.Players:GetPlayers()) do
				--task.wait(2)
				Saving(Plr)
			end
		end)
	end
end)