Data Stores (Saving and Loading)

I’m trying to figure out the most efficient way to save and load data, but I can’t seem to for Slaying Simulator. Anybody able to help? (I’ve tried looking up tons of forum threads and wikis – I still get data loss reports)

4 Likes

I think that you’ll always get data loss reports due to some players internet speeds. (Their connection to the game server)

3 Likes

Are there any specific methods though? Idk if my data saving techniques are on par with everybody else’s lol

If you can tell us what your method for saving and loading data is then we can tell you if it’s fine or how to improve it.

What I usually do is encode all of the data in a json table, pcall a save function. Works pretty good.

Encoding your tables is not necessary while using datastores; it’s automatically converted for you.

1 Like

This thread here covers everything you need: How would you use ROBLOX's datastorage API? - #3 by ForeverHD

4 Likes

There’s not enough information on this thread for us to be able to help you. When creating a support request, please include as much details as you can so we can help you better. For starters:

  • Please include details of your current implementation or a code sample.
  • Please indicate if you have made prior investigation to your data loss reports, or whether you’re just leaving it up to us to do that work for you. You should always investigate first before raising a question like this. This could be something you could solve easily alone.
  • If you are willing to share your implementation as it is right now, please post your code. If you do, you can also hop the thread over to #development-support:requests-for-code-feedback since the code would work but you’re asking how to make it “more efficient”.
2 Likes

Can you send your save and load functions? I have never gotten reports of data loss with any of my recent games after failing to understand how to save and load data properly the first time around.

this is probably horrendous but here we go
it saves every 120 seconds and when the player leaves and on game shutdowns
loads when you join the game

function m:SaveData(p)
	local data = m:GetPlayerData(p)
	local id = p.UserId
	local function saveData(store,value)
		local t = 0
		repeat
			t = t + 1
			local s,err = pcall(function()
				store:SetAsync(id,value)
			end)
			if not s then wait(1) end
		until s or t == 4
	end
	saveData(levelSave,data.Level)
	saveData(killsSave,data.Kills)
	saveData(gemSave,data.Gems)
	saveData(prestigeSave,data.Prestiges)
	saveData(itemSave,{Experience = data.Experience,
		CurrentSword = data.CurrentSword,
		Pets = data.Pets,
		Codes = data.Codes,
		Zones = data.Zones,
		Quest = data.Quest,
		CurrentAura = data.CurrentAura,
		Auras = data.Auras})
end

function m:LoadData(p)
	local id = p.UserId
	local levelSuccess,levelLoad = pcall(function()
		return levelSave:GetAsync(id)
	end)
	if not levelSuccess then
		wait(1)
		levelLoad = levelSave:GetAsync(id)
	end
	levelLoad = levelLoad or 1
	
	local killsSuccess,killsLoad = pcall(function()
		return killsSave:GetAsync(id)
	end) 
	if not killsSuccess then
		wait(1)
		killsLoad = killsSave:GetAsync(id)
	end
	killsLoad = killsLoad or 0
	
	local gemSuccess,gemLoad = pcall(function()
		return gemSave:GetAsync(id)
	end)
	if not gemSuccess then
		wait(1)
		gemLoad = gemSave:GetAsync(id)
	end
	gemLoad = gemLoad or 0
	
	local prestigeSuccess,prestigeLoad = pcall(function()
		return prestigeSave:GetAsync(id)
	end)
	if not prestigeSuccess then
		wait(1)
		prestigeLoad = prestigeSave:GetAsync(id)
	end
	prestigeLoad = prestigeLoad or 0
	
	local itemSuccess,itemLoad = pcall(function()
		return itemSave:GetAsync(id)
	end)
	if not itemSuccess then
		wait(1)
		itemLoad = itemSave:GetAsync(id)
	end
	itemLoad = itemLoad or {
			Quest = "None",
			CurrentSword = "Wooden Sword",
			Experience = 0,
			Pets = {},
			Codes = {},
			Zones = {},
			CurrentAura = "None",
			Auras = {}
	}
	
	players[p].Level = levelLoad
	players[p].Kills = killsLoad
	players[p].Gems = gemLoad
	players[p].Prestiges = prestigeLoad
	if itemLoad then
		for i,v in pairs(itemLoad) do
			players[p][i] = v
		end
	else
		players[p].Quest = "None"
		players[p].CurrentSword = "Wooden Sword"
		players[p].Experience = 0
		players[p].Pets = {}
		players[p].Codes = {}
		players[p].Zones = {}
		players[p].CurrentAura = "None"
		players[p].Auras = {}
	end
	m:UpdateLeaderstats(p)
end

sorry ab the code being in the message, I’m still new to devforum lol

oh it auto translates it

If I were you, I would completely redo your entire data save/load system. There are instances where it’s possible your data doesn’t save.

local function saveData(store,value) 
 local t = 0 
  repeat 
   t = t + 1 
   local s,err = pcall(function() 
    store:SetAsync(id,value) end) 
   if not s then 
    wait(1) 
   end 
until s or t == 4 end

One issue is that you’re using multiple saves and you stop saving after 4 attempts. Remove “t == 4” and use wait(6) instead. Honestly you should just put all your data into a table and use that in SetAsync.

local levelSuccess,levelLoad = pcall(function()
return levelSave:GetAsync(id)
end)
if not levelSuccess then
wait(1)
levelLoad = levelSave:GetAsync(id)
end
levelLoad = levelLoad or 1

local killsSuccess,killsLoad = pcall(function()
	return killsSave:GetAsync(id)
end) 
if not killsSuccess then
	wait(1)
	killsLoad = killsSave:GetAsync(id)
end
killsLoad = killsLoad or 0

local gemSuccess,gemLoad = pcall(function()
	return gemSave:GetAsync(id)
end)
if not gemSuccess then
	wait(1)
	gemLoad = gemSave:GetAsync(id)
end
gemLoad = gemLoad or 0

Along with multiple SetAsyncs will result in multiple GetAsyncs. Again if all you did was one table you would only need to save and load that one table of data.

As for loading with your current system. You’re only trying to load their data twice(one for the first time, and one for if it fails). If those fail then you simply don’t load their data.

Another thing is that both of these eat your allowed requests for SetAsync and GetAsync per minute. With more people in the server, you will go through how many requests you’re allowed to use per minute pretty quick especially if they fail. Also back when I said “wait(6)” that was for the repeated check due to a 6 second cooldown.

Conclusion. Please(I don’t want to sound too harsh but it’s my honest opinion), just remake your system and make all the data into one table. This way you only need one of each request at the minimum for saving and/or loading.

Another precaution is to keep track of players who join and when their data loads. For my games, if a player leaves before their data is loaded, the game won’t overwrite their data with a blank template as well as it will stop the current attempts at loading their data if it didn’t work the first time.

I have been seeing a lot of cases of developers complaining about lost data, but I question if its actually Roblox’s fault or if developers just aren’t taking the necessary precautions(I don’t want this to sound like an attack).

I gotcha, but I do have a quick question about the loading.
If the player is new and their data isn’t logged into the datastore, then the pcall for loading would make the success false, and there would be no way of telling if it was a fetch error or the player was new, so how would I put it on a loop to keep trying to load and know whether the player has played before?

You want to be storing all player data that you can in a single DataStore. Additionally, avoid random wait() calls. Don’t manually merge data in like that. Instead, use a generic table merge function to achieve true flexibility. I recommend defining a template and merging loaded data into a copy of the template.

You are not checking if data is loaded before saving! You’re going to want to fix this. This is very irresponsible in games where players can spend R$ to get in-game rewards.

Set a flag in the player’s data describing whether or not the data was loaded successfully.

The pcall won’t return false. Just check if the value you got from GetAsync is nil once the pcall returns true. Pcall simply checks for errors. Not having any data on a new player isn’t an error.

Tomorrow when I get out of bed I’ll write a basic setup for data.

Gotcha, thanks :grin:

How’s this?

function m:SaveData(p)
	local data = m:GetPlayerData(p)
	local id = p.UserId
	local function saveData(store,value)
		repeat
			local s,err = pcall(function()
				store:SetAsync(id,value)
			end)
			if not s then wait(6) end
		until s
	end
	if data.Loaded.Level then
		saveData(levelSave,data.Level)
	end
	if data.Loaded.Kills then
		saveData(killsSave,data.Kills)
	end
	if data.Loaded.Gems then
		saveData(gemSave,data.Gems)
	end
	if data.Loaded.Prestiges then
		saveData(prestigeSave,data.Prestiges)
	end
	if data.Loaded.Items then
		saveData(itemSave,{Experience = data.Experience,
			CurrentSword = data.CurrentSword,
			Pets = data.Pets,
			Codes = data.Codes,
			Zones = data.Zones,
			Quest = data.Quest,
			CurrentAura = data.CurrentAura,
			Auras = data.Auras})
	end
end

function m:LoadData(p)
	local id = p.UserId
	local function loadData(store,default)
		local returnedData = default
		repeat
			local success = pcall(function()
				local data = store:GetAsync(id)
				if data then returnedData = data end
			end)
			if not success then wait(6) end
		until success
		return returnedData or default
	end
	local levelLoad = loadData(levelSave,1)
	players[p].Loaded.Level = true
	local killsLoad = loadData(killsSave,0)
	players[p].Loaded.Kills = true
	local gemLoad = loadData(gemSave,0)
	players[p].Loaded.Gems = true
	local prestigeLoad = loadData(prestigeSave,0)
	players[p].Loaded.Prestiges = true
	local itemLoad = loadData(itemSave,{
			Quest = "None",
			CurrentSword = "Wooden Sword",
			Experience = 0,
			Pets = {},
			Codes = {},
			Zones = {},
			CurrentAura = "None",
			Auras = {}
	})
	players[p].Loaded.Items = true
	players[p].Level = levelLoad
	players[p].Kills = killsLoad
	players[p].Gems = gemLoad
	players[p].Prestiges = prestigeLoad
	if itemLoad then
		for i,v in pairs(itemLoad) do
			players[p][i] = v
		end
	else
		players[p].Quest = "None"
		players[p].CurrentSword = "Wooden Sword"
		players[p].Experience = 0
		players[p].Pets = {}
		players[p].Codes = {}
		players[p].Zones = {}
		players[p].CurrentAura = "None"
		players[p].Auras = {}
	end
	m:UpdateLeaderstats(p)
end

Sorry for the delayed post(two 10-hour shifts at work the past few days), but here is my method of saving player data. It does checks such as if its new player data and makes sure that the player has had their data loaded before it is attempted to be saved.

Other features include that data is saved automatically every 2 minutes and data is saved when the server shuts down.

--ChuckXZ

local DataLoaded = {} --Table of player ids for if their data was loaded

local DataStoreService = game:GetService("DataStoreService") --Call the service
local PlayerData = DataStoreService:GetDataStore("PlayerData") --The player DataStore
local DataKey = "_Data" --String for the key of data in setting/getting the player's data

function PlayerIsInGame(PlayerName) --Just checks if the player is in game
	return game.Players:FindFirstChild(PlayerName)
end

function NewDataTable() --Creates a table of data for out player
	return {
		--Just an example, table will be different depending on data
		Kills = 0,
		Deaths = 0,
		Money = 100,
		Wins = 0,
	}
end

function SaveData(Player,DataTable) --Save the player's data with a supplied table of their data
	
	if not DataLoaded[Player.UserId] then --Check if the user's data actually loaded before we try to save it
		warn("Player's data did not save as no data was loaded | " .. Player.Name)
		return --Kill the function as the player's data wasn't loaded in to save
	end
		
	--If we pass the test of whether they had data loaded in or not, we can continue
	
	local Pcalled = false --Flag for our pcall check
	
	warn("Saving player data | " .. Player.Name)
	
	repeat
		
		Pcalled = pcall(function() --Pcall check(s) so we don't run into issues with data saving
			PlayerData:SetAsync(Player.UserId .. DataKey, DataTable) --Attempt to save the data
		end)
		
		if not Pcalled then --If there was an error saving the player's data
			warn("Failed to save player data, Retrying | " .. Player.Name)
		end
		
		wait(6.1)
	until Pcalled --Do not stop until our pcall test runs perfectly
	
	warn("Data saved successfully! | " .. Player.Name)
	
end

function LoadData(Player) --Attempt to load the player's data
	
	local Pcalled = false --Flag for our pcall check
	local Data --The data we will soon get from out DataStore
	
	warn("Attempting to load player data | " .. Player.Name)
	
	repeat
		
		Pcalled = pcall(function() --Pcall check(s) so we don't run into issues with data no loading
			Data = PlayerData:GetAsync(Player.UserId .. DataKey) --Try to retrieve the player's data
		end)
		
		if not Pcalled then --If we experienced an error loading the player's data
			warn("Failed to load player's data, Retrying | " .. Player.Name)
		end
		
		wait(6.1)
	until Pcalled --Do not stop until our pcall test runs perfectly

	if PlayerIsInGame(Player.Name) then --Just incase the player left befire their data loaded
		DataLoaded[Player.UserId] = true --Let the server know that the data was successfully loaded
	end

	if Data then --Checks if the player has had a previous data save
		warn("Data successfully loaded! | " .. Player.Name)
	else --If the player doesn't have any data due to it being their first time here
		warn("New data detected! | " .. Player.Name)
		SaveData(Player,NewDataTable()) --Set the player up with some data
	end

	return Data --Return the data we retrieved(You can use it at this point)
	
end

--When a player joins
game.Players.PlayerAdded:Connect(function(Player)
	
	DataLoaded[Player.UserId] = false --Set it so that the server knows their data has not been loaded
	
end)

--When a player leaves
game.Players.PlayerRemoving:Connect(function(Player)
	
	SaveData(Player)
	
end)

--Loop to autosave data every 2 minutes
spawn(function()
	while wait(121) do --Loop every 2 minutes and save every player's data
		
		for _,Player in pairs(game.Players:GetPlayers()) do
			spawn(function()
				local Data = NewDataTable()
		
				--Fill in the table(or make a function to do it)
					
				SaveData(Player,Data)
			end)
		end
		
	end
end)
	
--Save everyone's data when the server shuts down
game:BindToClose(function()
	for _,Player in pairs(game.Players:GetPlayers()) do
		spawn(function()
			local Data = NewDataTable()
	
			--Fill in the table(or make a function to do it)
				
			SaveData(Player,Data)
		end)
	end
	wait(20) --20 seconds of leeway for saving player data
end)

EDIT: Just made edits that I didn’t catch

17 Likes

I have the same problem.