Data loss issues with datastores

Im unsure why this keep happening but sometimes here and there i lose data in my game
i dont know what the issue is and im unsure if its just roblox or my code is bad

local DSS = game:GetService("DataStoreService")
local ss = game:GetService("ServerStorage")
local sss = game:GetService("ServerScriptService")
local DataStore = DSS:GetDataStore("DataV1")


local playersleft = 0


game.Players.PlayerAdded:Connect(function(plr)
	playersleft = playersleft + 1
	
	local datafolder = Instance.new("Folder",plr)
	datafolder.Name = "data"
	
	local NumberValues ={
		"Spins",
		"Luck",
		"Gems",
	}
	local StringValues = {
		"Aura",
		"Trait",
	}
	
	for i,v in pairs(NumberValues) do
		local A = Instance.new("NumberValue", datafolder)
		A.Name = v
	end
	for i,v in pairs(StringValues) do
		local A = Instance.new("StringValue", datafolder)
		A.Name = v
	end
	
	
	
	
	local PlayerUserId = "Player_"..plr.UserId
	
	local Data
	local success, err = pcall(function()
		Data = DataStore:GetAsync(PlayerUserId)
	end)
	
	if success then
		if Data then
			plr:FindFirstChild("data").Trait.Value = Data.traitsave
			plr:FindFirstChild("data").Aura.Value = Data.aurasave
			plr:FindFirstChild("data").Spins.Value = Data.spinssave
			plr:FindFirstChild("data").Luck.Value = Data.lucksave
			plr:FindFirstChild("data").Gems.Value = Data.gemssave
		else
			plr:FindFirstChild("data").Trait.Value = "Traitlesss"
			plr:FindFirstChild("data").Aura.Value = "Auraless"
			plr:FindFirstChild("data").Spins.Value = 100
			plr:FindFirstChild("data").Luck.Value = 1
			plr:FindFirstChild("data").Gems.Value = 0
		end
	else
		print("why")
	end
end)

local Bindable = Instance.new("BindableEvent")

game.Players.PlayerRemoving:Connect(function(plr)
	local PlayerUserId = "Player_"..plr.UserId
	
	local Data = {
		traitsave = plr:FindFirstChild("data").Trait.Value;
		aurasave = plr:FindFirstChild("data").Aura.Value;
		spinssave = plr:FindFirstChild("data").Spins.Value;
		lucksave = plr:FindFirstChild("data").Luck.Value;
		gemssave = plr:FindFirstChild("data").Gems.Value;
	}
	
	
	local success, err = pcall(function()
		DataStore:SetAsync(PlayerUserId, Data)
		playersleft = playersleft - 1
		Bindable:Fire()
	end)
	
	if success then
		print("Saved Data")
	else
		print("Off Error")
		warn(err)
	end
	
end)

game:BindToClose(function()
	while playersleft > 0 do
		Bindable.Event:Wait()
	end
end)

the game loads data fine but sometimes rarely i get an error when a player is leaving the game

3 Likes

Are you hitting the limits of the data store service?

1 Like

wdym by limits? im not saving much data just a few values

2 Likes

Use update async, and compare data, this will prevent losing a lot of progress, you can add autosave too, soo player will loose only 3 minutes instead of 3 hours of progress, test your game inside place, not studio too

1 Like

Sometimes network calls fail, such as to datastores. Wrapping them in a pcall prevents an error crashing the script but if it fails the code won’t have run successfully and may need to be attempted again.
Using a repeat loop would allow you to retry saving the data in case the first attempt fails, which will catch some problems.

--basic format
repeat

  --code to save in datastore 

   if not success then 
      task.wait(3)
      Maxattempts += 1
   end
until success or Maxattempts == 5
1 Like

there are a lot of things I would change here.
A few easy changes:

Use UpdateAsync
Add auto saving
instead of using playersleft variable just use #game.players:GetPlayers() to get a 100% accurate number

1 Like

I always add this code too.

game:BindToClose(function()
  task.wait(--number from 1->2 works the best)
end)

I’d personally recommend just using a datastore module like SDM since they have features to reduce the chances of losing your data and more.

1 Like

Yeah most games (especially ones that save a lot of data, like fps games) often do this.

1 Like

You’ll benefit from using this instead:

Some quick tips:

1: Don’t use FindFirstChild unless you’re going to guarantee the child exists

The point of FindFirstChild is to ensure that object exists. Here, you use the function but not check if it exists. If it doesn’t exist, it will error. If this is on the client, it should use WaitForChild once, not on every line the object is used.

2: Avoid variable desync possibility in pcall

If the code fails to save your data, the playersleft variable will not change, causing something I like to call variable desync. It’s when your variables do not represent the situation accurately anymore for whatever reason. In this case, it’s because the code might not reach it. Instead, you should put this variable assignment outside the pcall to ensure it will always run. The event should also be fired outside of the pcall.

3: Avoid BindToClose in Studio

This code will be fine assuming the pcall doesn’t fail. If the pcall fails, the event will not fire, causing this to hang studio for 10-30 seconds. BindToClose stops waiting for the function to be finished after the 10-30 seconds. It would be annoying to wait this long! Even if the pcall doesn’t fail, it could still take time to save your data if roblox is down.

4: Pcall shouldn’t set any outside variables

This code will work, but to make it cleaner, you should instead return the new data:

local ran, result = pcall(function()
    return DataStore:GetAsync(PlayerUserId)
end)

print(ran, result) --> boolean?, table|string?

5: Avoid messy variables

Assuming the variable works completely, it is still not required. You should use #game.Players:GetPlayers() unless optimization is a serious concern in your project! If you use this multiple times in one place, consider making a variable to game.Players:GetPlayers() so you can access the players AND the amount of players easily. This table won’t update itself with new players.

1 Like

I would suggest replacing your current code with this:

local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local DataStore = DataStoreService:GetDataStore("DataV1")

local function onPlayerAdded(player)
	local success, data = xpcall(DataStore.GetAsync, warn, DataStore, "Player_"..player.UserId)

	if success then
		local dataFolder = Instance.new("Folder")
		dataFolder.Name = "data"
		dataFolder.Parent = player

		local Spins = Instance.new("NumberValue")
		Spins.Name = "Spins"

		local Luck = Instance.new("NumberValue")
		Luck.Name = "Luck"

		local Gems = Instance.new("NumberValue")
		Gems.Name = "Gems"

		local Aura = Instance.new("StringValue")
		Aura.Name = "Aura"

		local Trait = Instance.new("StringValue")
		Trait.Name = "Trait"

		if data then
			Spins.Value = data.Spins
			Luck.Value = data.Luck
			Gems.Value = data.Gems
			Aura.Value = data.Aura
			Trait.Value = data.Trait
		else
			Spins.Value = 100
			Luck.Value = 1
			Gems.Value = 0
			Aura.Value = "Auraless"
			Trait.Value = "Traitless"
		end

		Spins.Parent = dataFolder
		Luck.Parent = dataFolder
		Gems.Parent = dataFolder
		Aura.Parent = dataFolder
		Trait.Parent = dataFolder
	end
end

local function onPlayerRemoving(player)
	local dataFolder = player:FindFirstChild("dataFolder")

	if dataFolder then
		xpcall(DataStore.SetAsync, warn, DataStore, "Player_"..player.UserId, {
			Spins = dataFolder.Spins.Value,
			Luck = dataFolder.Luck.Value,
			Gems = dataFolder.Gems.Value,
			Aura = dataFolder.Aura.Value,
			Trait = dataFolder.Trait.Value
		})
	end
end

if RunService:IsStudio() then
	game:BindToClose(function()
		task.wait(1)
	end)
else
	game:BindToClose(function()
		local x, y = 0, 0

		local spawn = task.spawn
		local wait = task.wait

		local function onBindToClose(player)
			onPlayerRemoving(player)

			y += 1
		end

		for _, player in Players:GetPlayers() do
			x += 1

			spawn(onBindToClose, player)
		end

		repeat wait(0) until y == x
	end)
end

Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(onPlayerRemoving)

It should behave like your current version (as in the names are the same and such), but hopefully it will work more consistently and reliably. Do make sure that:

  • It’s a server Script
  • You have Studio access to API services enabled
  • You’re testing in a place that’s published to Roblox
  • You change the values while in server mode when play-testing (there should be a green border around the viewport if you have the latest version of Studio installed)
  • You change the values within server scripts during actual gameplay

Edit: @SenkoRogue I just noticed you’re using "Player_"..plr.UserId so I edited the code to fix that

1 Like

Yes! I forgot to mention this. By adding this when the server is closed it gives it a few extra second to save. This is especially important when testing in roblox studio.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.