Scripting HelpersARCHIVE
A little piece of Roblox history. Preserved for everyone, one question at a time.Read about the archive →
✓ ANSWER ACCEPTED

Data rarely not saving stats?

Hi, i'm having a issue with datastores. It occurs very rare that if a player leaves their stats will not save.

Stats script:

game.Players.PlayerAdded:Connect(function(player)
	local datastoreservice = game:GetService("DataStoreService")
	local data = datastoreservice:GetDataStore("Stats")
	local folder = Instance.new("Folder")
	folder.Name = "Stats"
	folder.Parent = player
	local intvalue = Instance.new("IntValue", folder)
	intvalue.Name = "Wins"
	intvalue.Value = data:GetAsync(player.UserId) or 0
	intvalue.Changed:Connect(function()
		data:GetAsync(player.UserId, intvalue.Value)
	end)
end)

Save script:

local datastoreservice = game:GetService("DataStoreService")
local data = datastoreservice:GetDataStore("Stats")

game.Players.PlayerRemoving:Connect(function(player)
	data:SetAsync(player.UserId, player.Stats.Wins.Value)
end)

game:BindToClose(function()
	wait()
end)

1 answer

✓ Accepted answer

wait() waits for 1/30 (0.03 seconds) which is sometimes not enough for data to save, you have 2 solutions here, set bigger number to wait in BindToClose function, to 2 for example or put another SetAsync into the function as sometimes not even wait(2) is enough:

local Players = game:GetService('Players')

...

game:BindToClose(function()
	for _, player in ipairs(Players:GetPlayers()) do
		data:SetAsync(player.UserId, player.Stats.Wins.Value)
	end
end)

That way you will save your data twice if you leave and the game closes, you might get a warning that key has been queued, you don't need to worry about it but if you don't like it, you can create table of players who's data has been saved and in BindToClose check if player is not in the table, example like

local datastoreservice = game:GetService("DataStoreService")
local data = datastoreservice:GetDataStore("Stats")

local Saved = {}

game.Players.PlayerRemoving:Connect(function(player)
	data:SetAsync(player.UserId, player.Stats.Wins.Value)

	Saved[player] = true
end)

game:BindToClose(function()
	for _, player in ipairs(Players:GetPlayers()) do
		if not (Saved[player]) then
			data:SetAsync(player.UserId, player.Stats.Wins.Value)
		end
	end
end)

Hover that does not have to always work, here is where you can (But don't have to) use your wait, it will make sure that player has been added to the table in most cases

...
game:BindToClose(function()
	wait(0.05)

	for _, player in ipairs(Players:GetPlayers()) do
		if not (Saved[player]) then
			data:SetAsync(player.UserId, player.Stats.Wins.Value)
		end
	end
end)

That should already work but, your data WILL be lost sometimes and it will happen a lot, trust me, in my game before i used better function i will show now, i used SetAsync and from 1000 joins i lost my data 7 times, sometimes even twice in a day, so this is why you switch SetAsync on UpdateAsync, it is not much different from SetAsync but it returns old data so you can check if new data is not corrupted, here is example of it:

game.Players.PlayerRemoving:Connect(function(player)
	local NewData = player.Stats.Wins.Value

	data:UpdateAsync(player.UserId, function(OldData)
		if (OldData) and (tonumber(OldData)) then
			if (OldData > NewData) then
				return nil
			end
		end

		return NewData
	end)

	Saved[player] = true
end)

It takes function as a second argument and whatever the function returns, will be saved, if it returns nil, nothing will be saved and it won't override previous data, in this example, you probably never want Wins to be lowered so it checks if previous data saved Wins is not higher than the data you are about to save and if it is then something is wrong with the new data so it will return nil. At the end it will return NewData because if nothing else returned, then it's all good. It also checks if OldData is a number to prevent errors in some cases.


I also see you saving data without pcalls, it's not that important in your case but it's better to have it, pcalls run given function and if the function errors, it returns the error and stops the function instead of stopping the whole script/scope from running. UpdateAsync and all other data store functions can fail, so you would wrap it into a pcall like this:

game.Players.PlayerRemoving:Connect(function(player)
	local NewData = player.Stats.Wins.Value

	local Success, Response = pcall(data.UpdateAsync, data, player.UserId, function(OldData)
		if (OldData) and (tonumber(OldData)) then
			if (OldData > NewData) then
				return nil
			end
		end

		return NewData
	end)

	print(Success, Response)
	
	--/Only add player into saved table if the function has suceeded
	--\if it will not succeed, thanks to BindToClose it will retry to save
	--\the data for the second time
	if (Success) then
		Saved[player] = true
	end
end)

pcall returns #1 Success (Whether the function did not error) and #2 Error? (Returns error if the function did error). In this case, you see i used data.UpdateAsync, data as 2 arguments, that's because data:UpdateAsync is called with : which means it requires self as it's first argument, since we called it with ., you need to put self argument manually, in this case just put data into next argument. The whole script at the end might look somewhat like this:

local datastoreservice = game:GetService("DataStoreService")
local data = datastoreservice:GetDataStore("Stats")

local Saved = {}

local function Save(player)
	local NewData = player.Stats.Wins.Value
	
	local Success, Response = pcall(data.UpdateAsync, data, player.UserId, function(OldData)
		if (OldData) and (tonumber(OldData)) then
			if (OldData > NewData) then
				return nil
			end
		end
	
		return NewData
	end)
	
	print(Success, Response)
	
	if (Success) then
		Saved[player] = true
	end
end


game.Players.PlayerRemoving:Connect(function(player)
	Save(player)
end)

game:BindToClose(function()
	wait(0.05)

	for _, player in ipairs(Players:GetPlayers()) do
		if not (Saved[player]) then
			Save(player)
		end
	end
end)

To not rewrite the saving function, i put it into Save local function. I tried to not change your script much so if you will try, you can achieve better function but this should work too.


Here are all the sources:

###Pcalls

###UpdateAsync ###pairs

1 comment

0

Thanks again!

This discussion is preserved as it was. New answers, comments, and votes are closed.