Assignment of multiple variables throughout a dictionary of dictionaries when unprompted

Hello, people.
I’m having quite an issue with dictionaries and the fact that trying to assign a value to only one variable in a dictionary (which itself is inside a dictionary) assigns it throughout multiple different dictionaries. For whatever reason, this issue isn’t present when modifying variables only a level deeper into the dictionary (which in my case would be “attributesCharacter”).
I would like for the event in the code below to only modify variables from the given character’s dictionary. Instead, it modifies every characters’ dictionary in the same manner.
I have ensured that the event is only being called once when needed and that there is no other script that could be messing with the result.

local players = game:GetService("Players")
local moduleFolder = game.ReplicatedStorage:WaitForChild("Modules")
local attributesModule = require(moduleFolder:WaitForChild("Attributes"))
local eventFolder = game.ReplicatedStorage:WaitForChild("Events")
local mouseButtonPressedEvent = eventFolder:WaitForChild("MouseButtonPressed")

-- "attributesModule" is equal to what's shown below.
attributesModule = {
	character1 = {
		Client = {...},
		Server = {...},
	},
	character2 = {
		Client = {...},
		Server = {...},
	},
	...,
}

mouseButtonPressedEvent.OnServerEvent:Connect(function(player)

	local character = player.Character
	local attributesCharacter = attributesModule[character]

	-- Assigns to every characters' dictionary because "Server" already exists (not intended)
	attributesCharacter["Server"]["NewVariable"] = "Value"
	-- Assigns to given character's dictionary (works as intended)
	attributesCharacter["Tester"] = {}
	-- Assigns to given character's dictionary ONLY since (I'd assume) it fails for all other characters since "Tester" doesn't exist in those dictionaries
	attributesCharacter["Tester"]["NewVariable"] = "Value"

	--[[
	-- I've noticed that reassigning existing variables removes the issue and
	-- allows for *only* variables in the given character's dictionary to be modified.
	-- However, you can't assign the variable to itself to prevent this issue
	-- from occurring which makes this information pointless. So *this*
	-- will stop it from assigning variables in other characters' dictionaries...
	attributesCharacter["Server"] = {}
	attributesCharacter["Server"]["NewVariable"] = "Value"
	-- but this won't. (Because why would it?)
	attributesCharacter["Server"] = attributesCharacter["Server"]
	attributesCharacter["Server"]["NewVariable"] = "Value"
	--]]

end)

-- After the event is called with "player1" as the argument,
-- "attributesModule" is equal to what's shown below.
attributesModule = {
	character1 = {
		Client = {...},
		Server = {
			NewVariable = "Value",
			...,
		},
		Tester = {
			NewVariable = "Value",
		},
	},
	character2 = {
		Client = {...},
		Server = {
			NewVariable = "Value",
			...,
		},
	},
	...,
}
-- For clarity, the new dictionary "Tester" and the new variable "NewVariable"
-- should only be present in "character1". However, "NewVariable" appears
-- within "character2" and every other characters' dictionary that follows.

I looked throughout the Roblox Studio forum and was unable to find anyone else with an issue like mine, so I decided to make this post.
I am new to posting in this forum, so I apologize if I’ve done something wrong or was unable to describe the issue well enough. If any other information is needed, please be sure to inform me.
I’d appreciate any kind of help since this technical issue has been bugging me for several hours.
Thank you.

From the code you presented, it almost looks like the server tables are the same across multiple characters.
Perhaps you are assigning one table to different characters upon creating the attribute table for each character?
if so i must warn you of the following:

TemplateTable = {
   att1 = 1
   att2 = 2
}

local object1 = TemplateTable
local object2 = TemplateTable

-- Object1 and Object2 are one and the same in memory, so:
object1.att1 = 3
print(object2.att1) -- will print 3, not 1.

-- my guess is everytime you create one of these:
character1 = {
	Client = {...},
	Server = {...},
    -- this table you assign is being used for other characters as well.
}

if that is the case, and you want to prevent the need to reassign variables, you may not define a template directly.
instead, you might want to use a function to generate such table

function ServerTable()
    return {
        ["key here"] = "default value here",
        ...
    }
end

each time this is called, a new unique table is generated in memory.
so they share a structure, but they are individual and don’t affect the other.

hope this solves it.

3 Likes

Tables are shared by reference, similar to how passing an Instance between variables still refers to the same Instance instead of creating a copy.


(Luau demo site.)

To fix this, you can use table.clone:

Note that table.clone is “shallow” - it does not clone other table values inside the table:

You can either re-assign the value to a new table (AKA change .List[1] = 3 etc to .List = { 3, 4 }) or call table.clone recursively using a custom helper function.

1 Like

Thank you, thank you, thank you (to the two of you)!
I feel a bit foolish for saying it, but I had completely forgotten about the concept of objects up until now!

To solve this problem, I went onto the official “Lua” site and found the code bellow which copies a table or dictionary without it being “shallow” or referencing other tables.

-- Save copied tables in `copies`, indexed by original table.
function deepcopy(orig, copies)
    copies = copies or {}
    local orig_type = type(orig)
    local copy
    if orig_type == 'table' then
        if copies[orig] then
            copy = copies[orig]
        else
            copy = {}
            copies[orig] = copy
            for orig_key, orig_value in next, orig, nil do
                copy[deepcopy(orig_key, copies)] = deepcopy(orig_value, copies)
            end
            setmetatable(copy, deepcopy(getmetatable(orig), copies))
        end
    else -- number, string, boolean, etc
        copy = orig
    end
    return copy
end

Afterward, I simply added this function when creating the dictionaries “Server” and “Client” as shown in the code bellow.

-- "variableServer" and "variableClient" are the dictionaries used in my original code
attributesModule[character]["Server"] = deepcopy(variablesServer)
attributesModule[character]["Client"] = deepcopy(variablesClient)

From there, the rest of the code was left untouched and now things are working as expected.
“NumericAbyss408” helped me understand and remember the fact that objects exist where as “Judgy_Oreo” led me to the solution by mentioning “table.clone” and helper functions.
The solution mark will be going to “Judgy_Oreo” for more directly leading me to the solution.

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