Weird table behaviour

It isn’t, I tested it. Here is the table code:

    module.clone = function(t1)
		local newTable = {};

		for k, v in pairs(t1) do
			newTable[k] = v;
		end;

		return newTable;
	end;

and I also know that it doesn’t since it has this error if they are linked, causing redundancy

The class module code:

    Utilities.import(getfenv(), "*"); --Imports all modules

	--// Special Services:
	local loadstring = loadstring;
	local table = table;

	--// Services:

	--// MAIN CODE //--	
	local module = {};

	-------------------	
	function module:RemoveSelf(...) --Remove Self is a function that allows both : and . to be called
		local arguments = {...}; --Packs the arguments
		
		if(module == arguments[1]) then table.remove(arguments, 1) end; --Checks if the first
--		argument is "module", if so then the function was called with a ":"
		
		return arguments;
	end;
	
	module.New = function(...)
		local rootArguments = module:RemoveSelf(...);
		
		local class = {};
		local metaData = {
			__type = "utilities-class",
			__id = tostring(os.time()) .. "_" .. tostring(math.random(1,10000))
		};
		local classMetatable = {};
		
		-------------------// Prototype of the class
		class.prototype = {};
		local prototypeMetaData = {
			__type = "utilities-class"
		};
		
		-------------------
		function class:RemoveSelf(...)
			local arguments = {...};
			if(arguments[1] == self) then
				table.remove(arguments, 1);
			end;
			
			return arguments;
		end;
		
		-------------------
		function class:InstanceOf(...)
			local arguments = {...};

			return arguments[1]["__holder-id"] == self.__id;
		end;
		
		-------------------
		function class.prototype:Extends(...)
			local arguments = {...};
			local root = self;
			function self:Super(...)
				local result = arguments[1].call(root, ...);
				
				for key, element in pairs(result) do
					if(not root[key]) then
						root[key] = element;
					end;
				end;
				
				if(not root.__extensions) then
					root.__extensions = {result};
				else
					table.insert(root.__extensions, result);
				end;
				
				for _, currentExtension in pairs(root.__extensions) do
					currentExtension:__setid(root.__id);
				end;
				
				return result;
			end;
			
			return self;
		end;
		
		-------------------
		function class:Extends(...) --Extends a class
			return class.prototype:Extends(...);
		end;
		
		-------------------
		function class:Super(...) --Calls super if the class is extended
			return class.prototype:Super(...);
		end;
		
		-------------------
		local updateConstructor = function(constructor) --This is the constructor that makes the
--		class, so when .new() is called this is the builder.
			
			local prototypeMetatable = getmetatable(class.prototype) or {};
			
			local builder = function(self, ...)
				local callArguments = {...};
				
				local extension = false;
				if(type(callArguments[1]) == "table" and callArguments[1]["__class-extension"] == true) then
					extension = true;
					self = callArguments[1]["__self"];
					table.remove(callArguments, 1);
				end;
				
				local clonedPrototype;
				if(extension == true) then
					clonedPrototype = table.clone(self);
					clonedPrototype["__holder-id"] = metaData.__id;
				else clonedPrototype = self end;
				
				clonedPrototype.__id = tostring(os.time()) .. "_" .. tostring(math.random(1,10000));
				function clonedPrototype:__setid(id)
					clonedPrototype.__id = id;
				end;
				
				local clonedPrototypeMetatable = {};
				
				------------------------
				function clonedPrototype:RemoveSelf(...)
					local arguments = {...};
					
					if(arguments and arguments[1] and (arguments[1] == self or arguments[1].__id == self.__id)) then
						table.remove(arguments, 1);
					end;

					return arguments;
				end;
				
				------------------------
				function clonedPrototype:InstanceOf(...)
					local arguments = {...};

					return self["__holder-id"] == arguments[1].__id;
				end;
				
				------------------------
				clonedPrototypeMetatable.__index = function(self, k)
					if(rawget(self, "__index")) then
						return rawget(self, "__index")(self, k) or rawget(self, k);
					end;
					
					return rawget(self, k);
				end;
				
				------------------------
				local bindKeys = {}; --It will bind 2 keys, if you modify one, the other one does
--				aswell
				clonedPrototypeMetatable.__newindex = function(self, k, v)
					if(extension and k == "__newindex") then
						local oldFunction = rawget(self, k);
						if(oldFunction) then
							local newFunction = function(self, k2, v2)
								v(self, k2, v2);
								oldFunction(self,k2,v2);
							end;
							rawset(self, k, newFunction);
						else
							rawset(self, k, v);
						end;
					else
						rawset(self,k,v);
					end;
					
					for _, keys in pairs(bindKeys) do
						if(table.includes(keys, k)) then
							for _, currentKey in pairs(keys) do
								rawset(self,currentKey,v);
							end;
						end;
					end;
					
					if(clonedPrototype.__newindex) then clonedPrototype.__newindex(self,k,v) end;
				end;
				
				------------------------
				function clonedPrototype:BindKeys(...)
					table.insert(bindKeys, {...});
					return self;
				end;
				
				------------------------// RETURN CONSTRUCTOR
				return constructor(clonedPrototype, callArguments) or clonedPrototype;
			end;
			
			------------------------
			prototypeMetatable.__call = builder;
			
			------------------------
			class.prototype.new = function(self, ...)
				local arguments = {...};
				
				if(self ~= class.prototype) then table.insert(arguments, 1, self) end;
				
				return class.prototype(table.unpack(arguments));
			end;
			
			------------------------
			class.prototype.call = function(self, ...)
				return class.prototype({
					["__class-extension"] = true,
					["__self"] = self
				}, ...);
			end;
			
			setmetatable(class.prototype, prototypeMetatable);
		end;
		
		------------------------// class.constructor = * will trigger the constructor.
		classMetatable.__newindex = function(self, k, v)
			if(k == "constructor") then
				updateConstructor(v);
			end;
		end;
		
		------------------------
		classMetatable.__call = function(self, ...)
			return class.prototype(...);
		end;
		
		------------------------
		class.new = function(...)
			return class(class:RemoveSelf(...));
		end;
		
		------------------------
		class.call = function(self, ...)
			return class.prototype.call(self, ...);
		end;
		
		-------------------//
		
		setmetatable(class, classMetatable);
		
		table.extend(class.prototype, prototypeMetaData);

		class.prototype = table.updated(class.prototype, function(self,k,v) --This function will update metaData if it is modified
			prototypeMetaData = class.prototype["__extend-table"];	
		end, true);

		prototypeMetaData = table.updated(prototypeMetaData, function(self,k,v) --This function will update class if the metadata
--			table is modified
			class.prototype["__set-extend-table"](prototypeMetaData);	
		end, true);
		
		-------------------
		
		class = table.extend(class, metaData);
		
		class = table.updated(class, function(self,k,v) --This function will update metaData if it is modified
			metaData = class["__extend-table"];
			if(k == "constructor") then
				updateConstructor(v);
				rawset(self, k, nil);
			end;
		end, true);
		
		metaData = table.updated(metaData, function(self,k,v) --This function will update class if the metadata
--			table is modified
			class["__set-extend-table"](metaData);	
		end, true);
		return class;
	end; module.new = module.New;
	
	return module;

Right, it wouldn’t do that if the value isn’t a table, but if it is a table, it will change on every single reference to that table.

local t = {
	t1 = true; -- won't change
	t2 = {} -- will change
}

local function clone(tbl)
	local newTable = {}
	for i,v in pairs(tbl) do
		newTable[i] = v
	end
	return newTable
end

local newTable = clone(t)
t.t2.test = true
t.t1 = false
print(newTable.t2.test) --> true
print(newTable.t1) --> true

So you are suggesting this only happens when declaring, and if so why? Because it dosent happen when I do this:

And I also know they dont overlap because the constructor function is a, well, function
so

function()
    return table
end;

That table will not be equal from any other table generated by the functions, and meanwhile yes, the clone only clones in the first depth. I mentioned it only because it is another reason it is unlikely, since the button is the first table

Try this.

Utilities.import(getfenv(), "*"); -- Imports all modules

-- // Special Services:
local loadstring = loadstring;
local table = table;

-- // Services:

-- // MAIN CODE //--	
local module = {};

-------------------	
function module:RemoveSelf(...) -- Remove Self is a function that allows both : and . to be called
    local arguments = {...}; -- Packs the arguments

    if (module == arguments[1]) then table.remove(arguments, 1) end-- Checks if the first
    --		argument is "module", if so then the function was called with a ":"

    return arguments;
end

module.New = function(...)
    local rootArguments = module:RemoveSelf(...);

    local class = {};
    local metaData = {
        __type = "utilities-class",
        __id = tostring(os.time()) .. "_" .. tostring(math.random(1, 10000))
    };
    local classMetatable = {};

    -------------------// Prototype of the class
    class.prototype = {};
    local prototypeMetaData = {__type = "utilities-class"};

    -------------------
    function class:RemoveSelf(...)
        local arguments = {...};
        if (arguments[1] == self) then table.remove(arguments, 1); end

        return arguments;
    end

    -------------------
    function class:InstanceOf(...)
        local arguments = {...};

        return arguments[1]["__holder-id"] == self.__id;
    end

    -------------------
    function class.prototype:Extends(...)
        local arguments = {...};
        local root = self;
        function self:Super(...)
            local result = arguments[1].call(root, ...);

            for key, element in pairs(result) do
                if (not root[key]) then root[key] = element; end
            end

            if (not root.__extensions) then
                root.__extensions = {result};
            else
                table.insert(root.__extensions, result);
            end

            for _, currentExtension in pairs(root.__extensions) do
                currentExtension:__setid(root.__id);
            end

            return result;
        end

        return self;
    end

    -------------------
    function class:Extends(...) -- Extends a class
        return class.prototype:Extends(...);
    end

    -------------------
    function class:Super(...) -- Calls super if the class is extended
        return class.prototype:Super(...);
    end

    -------------------
    local updateConstructor =
        function(constructor) -- This is the constructor that makes the
            --		class, so when .new() is called this is the builder.

            local prototypeMetatable = {};

            local builder = function(self, ...)
                local callArguments = {...};

                local extension = false;
                if (type(callArguments[1]) == "table" and
                    callArguments[1]["__class-extension"] == true) then
                    extension = true;
                    self = callArguments[1]["__self"];
                    table.remove(callArguments, 1);
                end

                local clonedPrototype;
                if (extension == true) then
                    clonedPrototype = table.clone(self);
                    clonedPrototype["__holder-id"] = metaData.__id;
                else
                    clonedPrototype = self
                end

                clonedPrototype.__id = tostring(os.time()) .. "_" ..
                                           tostring(math.random(1, 10000));
                function clonedPrototype:__setid(id)
                    clonedPrototype.__id = id;
                end

                local clonedPrototypeMetatable = {};

                ------------------------
                function clonedPrototype:RemoveSelf(...)
                    local arguments = {...};

                    if (arguments and arguments[1] and
                        (arguments[1] == self or arguments[1].__id == self.__id)) then
                        table.remove(arguments, 1);
                    end

                    return arguments;
                end

                ------------------------
                function clonedPrototype:InstanceOf(...)
                    local arguments = {...};

                    return self["__holder-id"] == arguments[1].__id;
                end

                ------------------------
                clonedPrototypeMetatable.__index = function(self, k)
                    if (rawget(self, "__index")) then
                        return
                            rawget(self, "__index")(self, k) or rawget(self, k);
                    end

                    return rawget(self, k);
                end;

                ------------------------
                local bindKeys = {}; -- It will bind 2 keys, if you modify one, the other one does
                --				aswell
                clonedPrototypeMetatable.__newindex =
                    function(self, k, v)
                        if (extension and k == "__newindex") then
                            local oldFunction = rawget(self, k);
                            if (oldFunction) then
                                local newFunction =
                                    function(self, k2, v2)
                                        v(self, k2, v2);
                                        oldFunction(self, k2, v2);
                                    end;
                                rawset(self, k, newFunction);
                            else
                                rawset(self, k, v);
                            end
                        else
                            rawset(self, k, v);
                        end

                        for _, keys in pairs(bindKeys) do
                            if (table.includes(keys, k)) then
                                for _, currentKey in pairs(keys) do
                                    rawset(self, currentKey, v);
                                end
                            end
                        end

                        if (clonedPrototype.__newindex) then
                            clonedPrototype.__newindex(self, k, v)
                        end
                    end;

                ------------------------
                function clonedPrototype:BindKeys(...)
                    table.insert(bindKeys, {...});
                    return self;
                end

                ------------------------// RETURN CONSTRUCTOR
                return constructor(clonedPrototype, callArguments) or
                           clonedPrototype;
            end;

            ------------------------
            prototypeMetatable.__call = builder;

            ------------------------
            class.prototype.new = function(self, ...)
                local arguments = {...};

                if (self ~= class.prototype) then
                    table.insert(arguments, 1, self)
                end

                return class.prototype(table.unpack(arguments));
            end;

            ------------------------
            class.prototype.call = function(self, ...)
                return class.prototype({
                    ["__class-extension"] = true,
                    ["__self"] = self
                }, ...);
            end;

            setmetatable(class.prototype, prototypeMetatable);
        end;

    ------------------------// class.constructor = * will trigger the constructor.
    classMetatable.__newindex = function(self, k, v)
        if (k == "constructor") then updateConstructor(v); end
    end;

    ------------------------
    classMetatable.__call = function(self, ...) return class.prototype(...); end;

    ------------------------
    class.new = function(...) return class(class:RemoveSelf(...)); end;

    ------------------------
    class.call = function(self, ...) return class.prototype.call(self, ...); end;

    -------------------//

    setmetatable(class, classMetatable);

    table.extend(class.prototype, prototypeMetaData);

    class.prototype = table.updated(class.prototype,
                                    function(self, k, v) -- This function will update metaData if it is modified
        prototypeMetaData = class.prototype["__extend-table"];
    end, true);

    prototypeMetaData = table.updated(prototypeMetaData,
                                      function(self, k, v) -- This function will update class if the metadata
        --			table is modified
        class.prototype["__set-extend-table"](prototypeMetaData);
    end, true);

    -------------------

    class = table.extend(class, metaData);

    class = table.updated(class,
                          function(self, k, v) -- This function will update metaData if it is modified
        metaData = class["__extend-table"];
        if (k == "constructor") then
            updateConstructor(v);
            rawset(self, k, nil);
        end
    end, true);

    metaData = table.updated(metaData,
                             function(self, k, v) -- This function will update class if the metadata
        --			table is modified
        class["__set-extend-table"](metaData);
    end, true);
    return class;
end;
module.new = module.New;

return module;

Can you show me the part that was modified?

I’m not sure I know what you mean when you say it only happens when declaring. Since tables are used in oop, they can be referenced from any number of scripts, and they will mutate on each reference, just like instances are-- instances are objects.

It has to be from only cloning in the first depth if you are only cloning in the first depth, otherwise you’d want to go through the table recursively and clone all the values over.

local function cloneRecursive(t)
    local nt = {}
    for i,v in pairs(t) do
        if type(v) == 'table' then
            nt[i] = cloneRecursive(v)
        else
            nt[i] = v
        end
    end
    return nt
end
local prototypeMetatable = {getmetatable(class.prototype) or {}}

I haven’t wrote a class system in some years so I genuinely cannot tell if this is needed or if an empty table might prevent overlapping issues, I’m not sure. But as per coding goes, it’s a matter of test and test again.

image
image

It returns this:
image

If you see, the modifications applied inside the constructor dont work

PluginManager is another class, it just happens to be called first in the script, it is no different and was working

Ah indeed, they do not. hm… Well I can only continue to look in such a case.

Also just want to add that this would be an instance of tables being mutated by all instances,

local mt = {}

local newmt = setmetatable({}, mt)

mt.test = true
print(getmetatable(newmt).test) --> true because the getmetatable returns the actual metatable, not just a clone of it

Let me try that, but this is 1 script, since it is called by a module then it is still 1 script. This table when created is created in a function

so:

function createTable()
    return {};
end;

The other tables won’t be the same, they are completely different. So the buttons can’t be the same

Tried this:

    module.clone = function(t1)
		local newTable = {};

		for k, v in pairs(t1) do
			if(type(v) == "table") then
				newTable[k] = module.clone(v);
			else
				newTable[k] = v;
			end;
		end;

		return newTable;
	end;

And then realized this will crash, and it did. The classes I have sometimes reference other class.

So the plugin class have a dockwidget class, this dockwidget class has “plugin” as a property. The plugin has the docks in a table

Can you send me the constructor for another class that works?
I’m honestly lost in this code since there’s very little commenting

All the classes work, it’s just the table has overridden all the other indexes with the last index.

Here is an event emitter code, a simple class that can emit events:

    local __ = getfenv();
	Utilities.import(__, "*"); --Imports all modules

	--// Special Services:
	local loadstring = loadstring;
	local table = table;

	--// Services:
	local TweenService = game:GetService("TweenService");
	local Class = __.Class;

	--// MAIN CODE //--	
	local EventEmitter = Class.new();
	
	EventEmitter.constructor = function(self, rootArguments)
		self.events = {};
		self["#listeners"] = 0;
		self.completed = 0;
		
		local getEvent = function(event)
			if(not self.events[event]) then
				local newEvent = Instance.new("BindableEvent"); newEvent.Name = event;
				self.events[event] = newEvent;
				self[event] = newEvent;
				return newEvent;
			else return self.events[event] end;
		end;
		
		-------------------// ADD LISTENER
		table.multiIndex(self, "AddListener", function(...)
			local arguments = self:RemoveSelf(...);
			
			local event, listener = arguments[1], arguments[2];
			assert(type(event) == "string", "Error at EventEmitter[AddListener]\n  Expected positional argument 1 (event) to be a string.");
			
			local newEvent = Instance.new("BindableEvent"); newEvent.Name = event;
			self.events[event] = newEvent;
			self[event] = newEvent;
			
			if(not listener) then return newEvent.Event;
			else
				assert(type(listener) == "function", "Error at EventEmitter[AddListener]\n  Expected argument 2 (listener) to be a function.")
				
				local active = true;
				newEvent.Event:Connect(function(...)
					if(active == true) then
						local result = listener(...);
						if(result == "__break" or result == "__break__") then
							active = false;
							self["#listeners"] = self["#listeners"] - 1;
						else self.completed = self.completed + 1 end;
					end;
				end);
				self["#listeners"] = self["#listeners"] + 1;
				
				local returnValue = {};
				table.multiIndex(returnValue, "Destroy", function()
					active = false;
					self["#listeners"] = self["#listeners"] - 1;
				end);
				return returnValue;
			end;
		end);
		
		-------------------// GET EVENT
		table.multiIndex(self, "GetEvent", function(...)
			local arguments = self:RemoveSelf(...);

			local event = arguments[1];
			assert(type(event) == "string", "Error at EventEmitter[AddListener]\n  Expected positional argument 1 (event) to be a string.");
			return getEvent(event).Event;
		end);
		
		-------------------// EMIT
		table.multiIndex(self, "Emit", function(...)
			local arguments = self:RemoveSelf(...);
			
			local event = arguments[1];
			assert(type(event) == "string", "Error at EventEmitter[AddListener]\n  Expected positional argument 1 (event) to be a string.");
			
			table.remove(arguments, 1);
			
			self.completed = 0;
			getEvent(event):Fire(table.unpack(arguments));
			
			repeat wait() until(self.completed >= self["#listeners"]);
			
			return getEvent(event).Event;
		end);
		
		-------------------// EMIT ASYNC
		table.multiIndex(self, "EmitAsync", function(...)
			local arguments = self:RemoveSelf(...);

			local event = arguments[1];
			assert(type(event) == "string", "Error at EventEmitter[AddListener]\n  Expected positional argument 1 (event) to be a string.");

			table.remove(arguments, 1);
			getEvent(event):Fire(table.unpack(arguments));

			return getEvent(event).Event;
		end);
	end;

I apologize if this has already been discussed, but what do you exactly mean by this?

Just this line:

------------------------// RETURN CONSTRUCTOR
return constructor(clonedPrototype, callArguments) or clonedPrototype;

For classes like plugins, that extends themselves with a function I need to actually return it. But if they are simple classes like a button I can just return the original since everything would be modified

Can you send me the table utilities your using? I’m mostly aiming to look at extend but the others are puzzling me a bit as well.

    module.extend = function(t1, t2)
		local newTable = module.clone(t1);
		
		local oldMetatable = getmetatable(t1);
		local metatable = module.clone(oldMetatable or {});
		
		metatable.__index = function(t,k)
			if(k == "__extend-table") then
				return t2;
			elseif(k == "__set-extend-table") then
				return function(value)
					t2 = value;
				end;
			else
				local result;
				xpcall(function()
					if(t2[k]) then
						if(typeof(t2) == "Instance" and type(t2[k]) == "function") then
							result = function(...)
								local arguments = {...};
								table.remove(arguments, 1);
								if(newTable == arguments[1]) then
									table.remove(arguments, 1);
								end;
								return t2[k](t2, table.unpack(arguments));	
							end;
						else
							result = t2[k];
						end;
					else
						if(oldMetatable) then result = oldMetatable.__index(t,k);
						else result = rawget(t1,k) end;
					end;
				end, function()
					if(oldMetatable) then result = oldMetatable.__index(t,k);
					else result = rawget(t1,k) end;
				end);
				return result;
			end;
		end;
		
		metatable.__newindex = function(t,k,v)
			xpcall(function()
				if(t2[k]) then
					if(typeof(t2) == "Instance") then
						t2[k] = v;
					else
						t2[k] = v;
					end;
				else
					if(oldMetatable) then oldMetatable.__newindex(t,k,v);
					else rawset(t1,k,v) end;
				end;
			end, function()
				if(oldMetatable) then oldMetatable.__newindex(t,k,v);
				else rawset(t1,k,v) end;
			end);
		end;
		
		setmetatable(newTable, metatable);
		
		return newTable;
	end;

    module.updated = function(t1, f, forceUpdate)
		if(not getmetatable(t1)) then
			setmetatable(t1, {
				__newindex = function(self, k,v)
					rawset(self, k, v);
					f(self,k,v)
				end;
			});
		else
			local oldMeta = getmetatable(t1);
			local newMeta = module.clone(getmetatable(t1));
			newMeta.__newindex = function(...)
				if(forceUpdate) then rawset(...) end;
				f(...);
				oldMeta.__newindex(...);
			end;
			
			setmetatable(t1, newMeta);
		end;
		
		return t1;
	end;

    module.multiIndex = function(t1, k, v) --This just makes so that .FunctionName .functionName and .functionname are the same thing
		t1[k] = v;
		t1[string.lower(string.sub(k,1,1)) .. string.lower(string.sub(k,2,string.len(k)))] = v;
		t1[string.lower(k)] = v;
	end;