Data Saving Problems

I created a data module that saves, and loads when you leave/rejoin the game. It also has autosaving every like 1 minute, but sometimes even though it’s saved a player’s data before it will completely wipe their data like nothing ever saved. I don’t know why it’s causing this problem but here’s the script in case anyone knows what could be causing the saving bug;

local module = {}

local data = game:GetService("DataStoreService"):GetDataStore("DarkMatter")
local ss = game.ServerStorage
local statFolder = game.ServerStorage:WaitForChild("PlayerStats")
local playerData = {}

local storage = game.ServerStorage
local plrStats = storage:WaitForChild("PlayerStats")
local usefulTools = storage:FindFirstChild("UsefulTools")

function loadStats(player)
	
	spawn(function()
				
		repeat wait() until player.Character or player == nil
		if player == nil then return end
		
		local race = player:FindFirstChild("RaceValue")
		local key = player.userId.."-key"
		local pData = data:GetAsync(key)
		local bp = player.Backpack
		local rio
		local urb
		local prog
		local weap
		local sFolder = statFolder:FindFirstChild(player.Name.."'s Stats")
		local elo = player:FindFirstChild("Elo")
		
		if sFolder then
			rio = sFolder:FindFirstChild("Riotous")
			urb = sFolder:FindFirstChild("Urbane")
			prog = sFolder:FindFirstChild("RacialProgress")
			weap = sFolder:FindFirstChild("Weapon")
		end
		
		if pData then
			
			race.Value = pData.race
			
			if pData.urbane then
				if urb then
					urb.Value = pData.urbane
				end
			end
			
			if pData.weapon then
				if weap then
					weap.Value = pData.weapon
				end
			end
			
			if pData.raceprogress then
				if prog then
					prog.Value = pData.raceprogress
				end	
			end
			
			if pData.elo then
				print(pData.elo)
				if elo then
					elo.Value = pData.elo
				end
			end
			
			if pData.riotous then
				if rio then
					print(pData.riotous)
					rio.Value = pData.riotous
				end
			end
		
			playerData[player] = pData -- define it in the playerData table 
			
		else
			-- no playerdata
		end
	
	end)
	
end

module.save = function(player)
	module.saveData(player)
end

module.loopdydoop = function()
	
	spawn(function()
		
		while wait(60) do
			
			for _,player in pairs(game.Players:GetPlayers()) do
				
				if player.Character then
					
					module.saveData(player, true) -- cleaned this up by just using this save func instead of copy pasting the entire thing lol
					
				end
			
			end
			
		end
	
	end)
	
end

module.giveTools = function(player)
	
	repeat wait() until player.Character or player == nil
	if player == nil then return end
	
	local bp = player.Backpack
	local key = player.userId.."-key"
	local pData = data:GetAsync(key)
	
	if pData then
		if pData.inventory then
		local foundTool = false
			for name,eh in pairs(pData.inventory) do
				if storage:WaitForChild("UsefulTools"):FindFirstChild(name) then
					foundTool = true
					if bp:FindFirstChild(name) == nil then
						local addTool = storage:WaitForChild("UsefulTools"):FindFirstChild(name):Clone()
						addTool.Parent = bp
					end
				end
			end
			if foundTool == true then end
		end
	end
	
end

module.addData = function(player)
	
	spawn(function()
		
		--local stats = player:WaitForChild("leaderstats")
		loadStats(player)
		
	end)
	
end

module.updateData = function(player)
	
end

module.saveData = function(player, autosave)
	
	spawn(function()
					
		local key = player.userId.."-key"
		local race = player:FindFirstChild("RaceValue")
		
		local osFolder = statFolder:FindFirstChild(player.Name.."'s Stats")
		local rio = osFolder:FindFirstChild("Riotous")
		local prog = osFolder:FindFirstChild("RacialProgress")
		local weap = osFolder:FindFirstChild("Weapon")
		local urb = osFolder:FindFirstChild("Urbane")
		local elo = player:FindFirstChild("Elo")
		local myStats = plrStats:WaitForChild(player.Name.."'s Stats")
		local storedTools = myStats:WaitForChild("StoredTools")
		
		local Data = {}
		
		if weap then
			Data.weapon = weap.Value
		end
		
		if urb then
			Data.urbane = urb.Value
		end
		
		if rio then
			Data.riotous = rio.Value
		end
		
		if elo then
			Data.elo = elo.Value
		end
		
		if prog then
			Data.raceprogress = prog.Value
		end
		
		if race then
			Data.race = race.Value
		end
		
		Data.inventory = {}
		
		for _,value in pairs(storedTools:GetChildren()) do
			if usefulTools:FindFirstChild(value.Name) then
				Data.inventory[value.Name] = true
			end
		end
				
		data:SetAsync(key,Data)
		
		if not autosave then -- this function was not called from loopy, so that means they are leaving
			if playerData[player] then
				playerData[player] = nil
			end
			
			if osFolder then
				osFolder:Destroy()
			end
		end
		
	end)
		
end

return module

You might want to take a look at this post regarding SetAsync() vs. UpdateAsync().

I don’t think that will fix the saving though, I feel as if something else is causing this issue.

1 Like

My apologies – I was unclear in my original post. It was merely intended to give you additional information about other improvements to your system, not as a specific response to your issue.

Yeah I read the topic updateasync would probably benefit my situation, but the problem is I save all my data to a table, so updating it is a problem because whenever I save tools to my game it adds another table, and adds the saved tools to it. I can’t update what isn’t there, so how can I work around that?

1 Like

One thing I see straight away is you are always assuming data will have no problems loading if it exists, but this is not always the case. You currently have this:

local pData = data:GetAsync(key)

which is fine, providing the data never fails to load. But what if it errors and causes the player’s data to be empty? I haven’t looked completely at the flow of your code, so this may not be the problem. But I don’t see any checks anywhere to ensure that the player’s data definitely loaded successfully before you save it. If you check on the GetAsync page, they make use a pcall so that in the case of an error, they can handle it correctly. In your case, you’d want to flag whether someone’s data has loaded successfully before saving it.

If your data structure is a table, you can easily append to that table and return that for it to be saved like so:

local defaultData = {} -- Default data in the case that it doesn't exist
local tableToAdd = {} -- Table to add to data

pointsDataStore:UpdateAsync(playerKey, function(oldValue)
	local newValue = oldValue or defaultData
	
	table.insert(newValue, tableToAdd)
	
	return newValue
end)

Again, with this, it’s best to have it in a pcall so you can handle data saving failures and also add a check to make sure the data actually loaded successfully before saving/updating it!