DataStore: tables cannot be cyclic

I wrote a datastore module, yet I keep getting the error above when attempting to write to the datastore on value change.

Module:

local dataStoreService = game:GetService("DataStoreService")
local playerDataStore = dataStoreService:GetDataStore("GameData")

local ON_COOLDOWN = false
local COOLDOWN_DURATION = 7


local function getDataTemplate()
	return {
		Gems = 0;
		Taps = 0;
		Rebirths = 0;
	}
end

local function deepCopy(original)
	local copy = {}
	for k, v in pairs(original) do
		if type(v) == "table" then
			v = deepCopy(v)
		end
		copy[k] = v
	end
	return copy
end

local function reconcileTable(target, template)
	for k, v in pairs(template) do
		if type(k) == "string" then -- Only string keys will be reconciled
			if target[k] == nil then
				if type(v) == "table" then
					target[k] = deepCopy(v)
				else
					target[k] = v
				end
			elseif type(target[k]) == "table" and type(v) == "table" then
				reconcileTable(target[k], v)
			end
		end
	end
end


local data = {}
data._playerData = {}


function data:GetPlayerKey(player)
	assert(type(player) == "userdata")
	
	local key = "Player_" .. player.UserId
	return key
end

function data:GetPlayerData(player)
	if self._playerData[player] then return self._playerData[player] end
	
	local playerKey = self:GetPlayerKey(player)
	local attempts = 0
	local playerData = nil
	local success,message
	
	repeat
		attempts = attempts + 1
		success,message = pcall(function() playerData = playerDataStore:GetAsync(playerKey) end)
		
		if not success then
			warn("Failed to load data ! Error: " .. message)
			wait(3)
		end
	until success or attempts >= 5
	
	if success then
		local template = getDataTemplate()
		if data then
			reconcileTable(data,template)
			self._playerData[player] = data
			return data
		else
			print("New Player")
			
			self._playerData[player] = template
			return template
		end
	else
		warn("Failed to load PlayerData. Using default template...")
		self._playerData[player] = getDataTemplate()
		return getDataTemplate()
	end
end

function data:FetchData(player)
	if self._playerData[player] then
		return self._playerData[player]
	else
		warn("No data exists for Player " .. player.Name .. ".")
		return getDataTemplate() or nil
	end
end

function data:UpdateValue(player,valName,newVal)
	print("Update Called")
	if not self._playerData[player] then return end
	if not self._playerData[player][valName] then print("UH OH!")return end
	
	self._playerData[player][valName] = newVal
	print("Updated !")
	
	if not ON_COOLDOWN then
		ON_COOLDOWN = true
		local key = self:GetPlayerKey(player)
		local success,message = pcall(function() playerDataStore:SetAsync(key, self._playerData[player]) end)
		if not success then
			warn("Failed to SetAsync PlayerData ! Error: " .. message)
		end
		coroutine.resume(coroutine.create(function()
			wait(COOLDOWN_DURATION)
			ON_COOLDOWN = false
		end))
	end
end

function data:SavePlayerData(player)
	if not self._playerData[player] then return end
	local newData = self._playerData[player]
	self._playerData[player] = nil
	
	local attempts = 0
	local success,message
	local key = self:GetPlayerKey(player)
	
	repeat
		attempts = attempts + 1
		success,message = pcall(function() playerDataStore:UpdateAsync(key, function(old) return newData end) end)
		
		if not success then
			warn("Failed to save PlayerData ! Error: " .. message)
			wait(7)
		end
	until success or attempts >= 5
end

return data

No errors, except in the output.

There is no number either in the message.

Your error is suppressed by a pcall. Which line number is sending the warning?

It’s above, I’ve quoted the part that errors, specifically, it’s this line:

warn("Failed to SetAsync PlayerData ! Error: " .. message)

The error means a value in your table is holding a reference to the table itself.

tabA = {}
tabB = {tabA}

tabA[1] = tabB

If you then sent tabA or tabB to the store, it would be rejected with the cyclic table error.

1 Like

I’ve had a look through the script but I cannot see where I’m setting that reference?

In studio if you print() a table it will render the table in the output window to help visualise and see all of the elements in the table.

I recommend doing that with the player data table to check where the issue is.