Feedback on datastore module

I’d like to see if anyone has any thoughts on how to improve my datastore module. In particle, with regard to .refreshData(), is there a better way to implement this.

local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("PlayerData")

local eventsFolder = game:GetService("ReplicatedStorage"):WaitForChild("Events")

local pcallGetAsync = function(userId)
	local rawSave
	local success, issue = pcall(function()
		rawSave = dataStore:GetAsync(tostring(userId))
	end)
	return success, issue, rawSave
end

local pcallSetAsync = function(userId, data)
	local success, issue = pcall(function()
		dataStore:SetAsync(tostring(userId), data)
	end)
	return success, issue
end

local module = {}

module.defaultTable = {
	gameInfo = {
		jump = 0;
		checkpoint = 0;
	};
	inventory = {
		items = {
			revives = 0;
		};
		titles = {
			{
				name = "Cool";
				price = 500;
				rarity = 1;
			}
		};
	};
}

module.dataCache = {}

module.refreshData = function(rawSave)
	local defaultTable = module.defaultTable
	local currentTable = rawSave
	if currentTable == nil then
		currentTable = {}
	end
	for key, value in pairs(defaultTable) do
		if currentTable[key] == nil then
			currentTable[key] = value
		elseif type(value) == "table" then
			for key2, value2 in pairs(value) do
				if currentTable[key][key2] == nil then
					currentTable[key][key2] = value2
				elseif type(value2) == "table" then
					for key3, value3 in pairs(value2) do
						if currentTable[key][key2][key3] == nil then
							currentTable[key][key2][key3] = value3
						elseif type(value3) == "table" then
							for key4, value4 in pairs(value3) do
								if currentTable[key][key2][key3][key4] == nil then
									currentTable[key][key2][key3][key4] = value4
								elseif type(value4) == "table" then
									for key5, value5 in pairs(value4) do
										if currentTable[key][key2][key3][key4][key5] == nil then
											currentTable[key][key2][key3][key4][key5] = value5
										end
									end
								end
							end
						end
					end
				end
			end
		end
	end
	return currentTable
end

module.pullFromCloud = function(player)
	local userId = player.UserId
	local success, issue, rawSave = pcallGetAsync(userId)
	if success == false then
		warn("An error occurred while loading data for player "..tostring(player.Name)..": "..issue)
		wait(2)
		success, issue, rawSave = pcallGetAsync(userId)
		if success == false then
			warn("Another error occurred while loading data for player "..tostring(player.Name)..": "..issue)
			player:Kick("An issue occurred while loading your data. Please rejoin in a few minutes or try again later. Specifically: "..issue)
		else
			return rawSave
		end
	else
		return rawSave
	end
end

module.pushToCloud = function(player)
	local userId = player.UserId
	local cachedSave = module.dataCache[userId]
	module.dataCache[userId] = nil
	local success, issue = pcallSetAsync(userId, cachedSave)
	if success == false then
		warn("An error occurred while saving data for player "..tostring(player.Name)..": "..issue)
		wait(2)
		success, issue = pcallGetAsync(userId)
		if success == false then
			warn("Another error occurred while loading data for player "..tostring(player.Name)..": "..issue)
			wait(2)
			success, issue = pcallGetAsync(userId)
			if success == false then
				error("A critical error occurred while attempting to save data for player "..tostring(player.Name)..": "..issue)
			end
		end
	end
end

module.getCache = function(player)
	local userId = player.UserId
	if module.dataCache[userId] == nil then
		local rawSave = module.pullFromCloud(player)
		local refreshedSave = module.refreshData(rawSave)
		module.dataCache[userId] = refreshedSave
	end
	return module.dataCache[userId]
end

module.updateCache = function(player, transformFunction)
	local cachedSave = module.dataCache[player.UserId]
	local modifiedSave = transformFunction(cachedSave)
	module.dataCache[player.UserId] = modifiedSave
	eventsFolder:WaitForChild("UpdateData"):InvokeClient(player, modifiedSave)
end

return module

A few notes:

  • I chose to use caching so intensively because my game will be rapidly updating the values, and thus would not be able to easily fit into the traditional datastore limits.
  • defaultTable will reflect the lastest format of the save data. This makes it easy to add new features.
1 Like

Your chained if/for statements are very messy. A recursive method would be better for this, and would only require you to type the if statement and for statement once (instead of having 5 keys and 5 values).

This module uses a recursive system which is similar to what you’ll want to do. I suggest taking a look.

3 Likes

Thanks for the link! I knew that I needed some sort of recursive system, but I wasn’t sure how exactly that was supposed to look. Any thoughts on the rest of the module?

1 Like

You can also minimize your pushToCloud function by just using a for loop:

local success
local issue
for i = 1, 3 do
     -- save data
     success, issue = pcallGetAsync(userId)
     if success then break end
     if issue then
          if i == 1 then
             -- Handle first fail etc
          elseif i == 2 then
          elseif i == 3 then
          end
     end
end
2 Likes

In addition to what @twinqle said, you can also use a recursive method for verifying the data to ensure all of the values are there (which I assume is what you’re trying to do)

Here’s what I’ve done personally and have had success with:

	local function verifyCurrentTable(tableToVerify, currentIteration) -- you want to pass the table to verify as your defaultTable
		for i,v in pairs(currentIteration) do 
			if not tableToVerify[i] then -- If it doesn't find the index inside of the tableToVerify then
				tableToVerify[i] = v -- set it to the default value which would derive from your defaultTable
			elseif type(v) == 'table' then -- If it's a table then
					tableToVerify[i] = verifyCurrentTable(tableToVerify[i], v) -- call the verifyCurrentTable function with the new index, and v would be the current nested table
				end
			end
		end
		return tableToVerify
	end

	local dataFolder = Instance.new('Folder')
	dataFolder.Parent = player
	dataFolder.Name = 'PlayerData'

	local newData = verifyCurrentTable(data, defaultData)
2 Likes

Try replacing the if statements by utilizing tables if you can. They massively decrease the size of these if statements if implemented correctly.

Another thing is that all DataStore calls will convert numbers into strings automatically for the key. You do not have to worry about that. I had the same ‘problem’ when I started scripting.

Another tip overall to your coding is to separate some lines of code using just empty lines… It’s easier to read, easier to change stuff, wayy better.

1 Like