Two different tables syncing values

I’m having an issue with tables syncing values even though they are not supposed to sync.
I have a module that stores a shallow copy of a table. The module has a function that will return said table. But when a script changes the value of the copied table, the original table will also change. I am unable to figure out why.

Here is a small snippet of my code:

function ClientSaveData:GetData()
	return ClientSaveData._Data
	--[[
	For testing, it should return:
	{
		SaveFiles = {
			[1] = {- -}
		}
	}
	]]--
end

function ClientSaveData:GetSaveFileFromId(SaveFileId: number): (any?, number?)
	if not ClientSaveData._Data then return end
	for i, saveFile in ipairs(ClientSaveData._Data.SaveFiles) do
		if saveFile.SaveFileId ~= SaveFileId then continue end
		return saveFile, i
	end
end

function ClientSaveData:LoadSaveFile(SaveFileId: number, SaveFile: any?)
	if ClientSaveData._CurrentSaveFile then
		error("A save file is already loaded.")
	end
	
	local SaveFile = SaveFile or ClientSaveData:GetSaveFileFromId(SaveFileId)
	if not SaveFile then return end
	local SaveFileCopy = table.clone(SaveFile)
	ClientSaveData._CurrentSaveFile = SaveFileCopy

	return ClientSaveData._CurrentSaveFile
end

function ClientSaveData:GetCurrentSaveFile()
	return ClientSaveData._CurrentSaveFile
end

A separate script which loads the save file:

ClientSaveData:LoadSaveFile(1)

Another script, separate from the previous one:

local OriginalSaveFile = ClientSaveData:GetData().SaveFiles[1]
local SaveFile = ClientSaveData:GetCurrentSaveFile()
SaveFile.Test = true -- Will also change OriginalSaveFile.Test to true
print(SaveFile == OriginalSaveFile) -- Will print false

Does anyone know the reason why the two different tables sync? This issue breaks my game so I really need a fix for it.

I think you need to do another shallow copy when you return the value from GetCurrentSaveFile().

function ClientSaveData:GetCurrentSaveFile()
	return table.clone(ClientSaveData._CurrentSaveFile)
end

I can’t do that since I need other scripts to update the table globally. If I do that, any changes to the table from other scripts will not update.

Can you do the clone in the calling file? That will leave it as a reference for other scripts but allow you to modify the local copy here.

local OriginalSaveFile = ClientSaveData:GetData().SaveFiles[1]
local SaveFile = table.clone(ClientSaveData:GetCurrentSaveFile())
SaveFile.Test = true -- Will also change OriginalSaveFile.Test to true
print(SaveFile == OriginalSaveFile) -- Will print false

I still can’t do that, it will result in the same issue as earlier when returning another copy of the table.

I found a cheeky solution. Instead of making a copy of the table in the same run context. I used a remote function that will clone the table on a different run context, then return it.

function ClientSaveData:LoadSaveFile(SaveFileId: number, SaveFile: any?)
	if ClientSaveData._CurrentSaveFile then
		error("A save file is already loaded.")
	end
	
	local SaveFile = SaveFile or ClientSaveData:GetSaveFileFromId(SaveFileId)
	if not SaveFile then return end
	local SaveFileCopy = CreateServerCopyOfTable:InvokeServer(SaveFile)
	ClientSaveData._CurrentSaveFile = SaveFileCopy

	return ClientSaveData._CurrentSaveFile
end

Server:

CreateServerCopyOfTable.OnServerInvoke = function(_, t)
	return table.clone(t)
end

I’m glad you found a solution.

I’ve had a similar problem where one script was modifying the values passed from a module and it can be hard to track down. I ended up using deep clones whenever returning table values from the module and then adding a set function which made the modification of the internal data more explicit. It results in more data copying but makes debugging a lot easier.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.