Thoughts on my global data-saving script

I’ve decided to make a global data-saving script for a game that I work on. This is mainly done to fix the datastore issues the game has been having and combining it into one helps tremendously.

I have each script that requires a data saving / receiving to fire a bindablefunction to receive it. I added a total of attempts for each data saving /receiving. I haven’t tested the attempts yet as I don’t know how I can make it error (if anyone knows how please let me know)

After the three attempts, if data fails to save it will send a webhook to Discord and will allow the developers to manually add any data in a last resort situation.

My code:

local Event = script.StoreData

local DataStorage = {}
local Webhook = "hidden"
local HttpService = game:GetService('HttpService')
local MaxAttempts = 3

function DataStorage:RecordDataLoss(player, type1, data)
	local content
	if type1 == "SetAsync" then
		local data_send = " "
		
		for i, v in next, data do
			data_send = data_send .. i .."  : ".. tostring(v).. " "
		end
		
		content = {
			['content'] = "Type of data loss: "..type1.. " Data lost for player: ".. player.Name .. " ("..player.UserId..') '.. data_send
		}
	elseif type1 == "GetAsync" then
		content = {
			['content'] = "Type of data loss: "..type1.. " if data loss is GetAsync then no data will be lost, pew pew"
		}
	end
				
	content = HttpService:JSONEncode(content)
	HttpService:PostAsync(Webhook, content)
end

--local data = {money = 100, rebirth = 3}
--DataStorage:RecordDataLoss(game.Players.SovereignObesity, "SetAsync", data)
		
function DataStorage:SetData(player, ds, key, data, attempts)
	if not player:FindFirstChild('DSFail') then
		local success, data = pcall(function()
			ds:SetAsync(key, data)
		end)
			
		if success then
			warn('Success in saving data')
			return true
		else
			warn('Failed to save data')
			DataStorage:RecordDataLoss(player, "SetAsync")
			
			if attempts >= 0 then
				attempts = attempts - 1
				return DataStorage:SetData(player, ds, key, data, attempts)
			else
				return false
			end
		end
	end
end
	

function DataStorage:GetData(player, ds, key, attempts)
	local dataToInput
	
	local success, data = pcall(function()
		dataToInput = ds:GetAsync(key)
	end)
	
	if success then
		if not dataToInput then
			warn('Failed to get dataToInput (no data exists in key: '..key..')')
			return nil
		else
			warn('Success in getting data')
			return dataToInput
		end
	else
		warn('Failed to get data')
		local a = Instance.new('BoolValue', player)
		a.Name = "DSFail"
		
		DataStorage:RecordDataLoss(player, "GetAsync")
		
		if attempts >= 0 then
			attempts = attempts - 1
			return DataStorage:GetData(player, ds, key, data, attempts)
		else
			player:Kick('Failed to load data')
			return false
		end
	end
end

--local args = {'text'}
--local args2 = unpack({args})
--print(args[1])


function Main(Type, ...)
	local args = unpack({...})
	
	if args.Player then
		warn('Got player')
		if Type == "SetData" then
			warn('Found type 1')
			return DataStorage:SetData(args.Player, args.Datastore, args.Key, args.Data, MaxAttempts)
		elseif Type == "GetData" then
			warn('Found type 2')
			return DataStorage:GetData(args.Player, args.Datastore, args.Key, MaxAttempts)
		else
			warn('No type given')
		end
	end
end
3 Likes

I would avoid using recursion for DataStorage:SetData. Instead a for loop counting upto the max retry would be better.

I often see people not using pcall correctly. It takes a function followed by the arguments to run that function.
You can use

local suc, data = pcall(ds.GetAsync, ds, key) -- ds is needed as it is a : 
2 Likes

i’ll be sure to try this, thank you!

Why though? If you make sure that you have a valid basecase it should be fine right? Or is there something I’m missing here?

I am not sure what you are asking?

Why you’d avoid using recursion in such a scenario. Sorry if the question wasn’t clear enough.

That is not “correct” usage of pcall. It just serves to make code less readable.

1 Like