Thoughts on my DataStore module

Hello, Developers!

I made a new DataStore module for my game, and I’d like to get your thoughts on it!

Thanks in advance. :slight_smile:

local module = {}

local PlayerData = game:GetService("DataStoreService"):GetDataStore("PlayerData", 2)

module.LoadData = function(player, PlayerFolder)
   
   local Data
   local success, err = pcall(function()
   	Data = PlayerData:GetAsync(player.UserId)
   end)
   
   if not success then
   	warn(err)
   	player:Kick("Error loading data: Please rejoin the game")
   end
   
   if Data then
   	PlayerFolder.Credits.Value = Data.Credits
   	PlayerFolder.Wins.Value = Data.Wins
   	PlayerFolder.DataId.Value = Data.DataId
   	
   	for i,v in pairs(Data.Codes) do
   		local t = Instance.new("BoolValue")
   		t.Name = v
   		t.Parent = PlayerFolder.Codes
   	end
   	
   	for i,v in pairs(Data.Backpack) do
   		local t = Instance.new("BoolValue")
   		t.Name = v
   		t.Parent = PlayerFolder.Backpack
   	end
   	
   else
   	PlayerFolder.Credits.Value = 0
   	PlayerFolder.Wins.Value = 0
   	PlayerFolder.DataId.Value = 0
   end
   
end

module.SaveData = function(player, PlayerFolder)
   
   local ValuesToSave = {
   	Credits = PlayerFolder.Credits.Value;
   	Wins = PlayerFolder.Wins.Value;
   	DataId = PlayerFolder.DataId.Value;
   	Codes = {};
   	Backpack = {};
   	
   }
   	
   for i,v in pairs(PlayerFolder.Codes:GetChildren()) do
   	table.insert(ValuesToSave.Codes, v.Name)
   end
   
   for i,v in pairs(PlayerFolder.Backpack:GetChildren()) do
   	table.insert(ValuesToSave.Backpack, v.Name)
   end
   	
   local success, err = pcall(function()

   	PlayerData:UpdateAsync(player.UserId, function(oldValue)
   		local prevData = oldValue or {DataId = 0}
   		if ValuesToSave.DataId == prevData.DataId then
   			ValuesToSave.DataId = ValuesToSave.DataId + 1
   			return ValuesToSave
   		else
   			return nil
   		end
   	end)

   end)
   
   if not success then
   	warn(err)
   end
   
end

return module
1 Like

You will want to be very careful about this. In the case of a DataStore failure, your game will become unplayable since most players will be unable to load their data and thus be kicked. What is normally recommended is to store the failed loads, notify the player that their stats won’t be saved due to the failed load, and not save any data. Example:

local FailedLoads = {}

...
	--If the load failed, alert the player and make the stats unable to be saved.
	if not success then
		warn(err)
		--TODO: Alert player stats won't save.
		FailedLoads[player] = true
	end
...

...
module.SaveData = function(player, PlayerFolder)
	--Don't save if the player failed to load.
	if FailedLoads[player] then
		return
	end
...
2 Likes

Thanks, I will make sure to change this!

It might look bad to the player if they get kicked from the game very soon after they join - they might think your game is broken or bad, even if it’s just this 1 datastore call that didn’t work. I recommend retrying the datastore call a few times before kicking the player - a simple for loop would work, with maybe waiting a few seconds between each iteration. Of course, it’s not good to loop many times when your datastore budget is low. To check how many datastore calls you still have left, you can use DataStoreService | Documentation - Roblox Creator Hub. Depending on how many datastore calls you have left, you can decide how many times you want to loop (for example, if you normally loop 3 times, but you only have 2 GetAsync datastore calls left, you could choose NOT to do any extra loops in order to save datastore calls). Hope this helps!

Consider making full use of pcalls by cutting down your variable amount and boosting your readability. Instead of assigning an upvalue’s value, you can actually return the results in a pcall function or wrap a method call directly (the latter is more preferred).

local success, result = pcall(PlayerData.GetAsync, PlayerData, player.UserId)
1 Like

So would I do:

local success, result = pcall(PlayerData.GetAsync, PlayerData, player.UserId)
PlayerFolder.Credits.Value = result.Credits
-- etc

EDIT: Tried it, and it works. Thanks!