Is __index for copying better than Deepcopying?

Some background information:
I am creating a system that manages player info in the AeroGameFramework. This info includes information on how their game session is progressing [Basically their money, the number of resources they have collected, etc].

Typically I would had used AeroGameFramework’s boiler plate .new function which looks like this:

local Class = {}
Class.__index  = Clas
function Class.new()
	local self = setmetatable({
        ["Key"] = "Number", --Example
	}, PlayerClass)
	return self
end

However, I want to be able to send this information to the client which I can’t because you can not send metatables through RemoteEvents. So I’d create a deepcopy of what I want to send every time to want to send it to the client!

So I want to avoid that by replacing my old .new functions with a new function that uses deep copying rather than metamethods.
The problem is I am unsure if replacing the old system would cause any problems. A bit of feedback would be appreciated.

Here is the copy function:

local function CopyTable(t)
	assert(type(t) == "table", "First argument must be a table")
	local tCopy = table.create(#t)
	for k,v in pairs(t) do
		if (type(v) == "table") then
			tCopy[k] = CopyTable(v)
		else
			tCopy[k] = v
		end
	end
	return tCopy
end

TL:DR
Should I worry about replacing __index with copying?

I don’t think AGF has much to do with this. Your first code block is the standard approach to OOP in Lua, framework or no.

To clarify, are you saying that instead of this:

local MyClass = {}
MyClass.__index = MyClass

function MyClass.new()
    return setmetatable({
        ["Key"] = "Number"
    }, MyClass)
end

function MyClass:DoSomething()
    print("Something")
end

Are you asking if you can/should do this?:

local MyClass = {}

local function CopyTable(t) --[[ etc... ]] end

function MyClass.new()
    local self = CopyTable(MyClass)
    self["Key"] = "Number"
    return self
end

function MyClass:DoSomething()
    print("Something")
end
2 Likes

Yes. That is exactly what I am asking. If I can use the CopyTable function instead of the usual setmetatable and __index.

The best way to know is to try it out :slight_smile:

Well, no one’s gonna break down your door telling you it’s the wrong way to code :slight_smile:

It would work as expected.

That being said, I don’t recommend it.

  • You’re allocating extra memory you don’t need – every instance now also keeps copies (at least of the keys) of all the members of its class.

  • If the class changes (some “static” member for example) changes at runtime, the instances will not pick up on that change, since they have their own copies.

  • I’m not sure what the issue is in the first place – you shouldn’t be sending metatables to the client, because you don’t (or at least shouldn’t) send classes over the wire, you send objects. Objects have their own state anyways (the Key = "Number" part), so why is the metatables not being copied a problem?

  • When I said

    you send objects

    That was sort of a lie. ROBLOX pretends like you send objects. Internally, they serialize that object (i.e. turn the table into a string) and send the string. On the other side, they parse the string back into a table.

    That comes with its own limitations, and also the serialization includes things like private member variables that you might not necessarily want or care to send over the network.

    … Instead, I would define some MyClass:Serialize() method on any classes that you want to be able to send over the network that turns the class into a compact string. Then, when you want to send the data, there’s no need for a deep copy. You just do remoteEvent:InvokeServer(object:Serialize()) and ROBLOX sends the string directly.

    Add a Deserialize method as well that the client can call which takes in a string in the same format and spits out an instance of MyClass, and you’re good to go.

    Bonus points if you use string.pack/unpack/packsize.

1 Like
I wrote you a toy example. It's not tested, but demonstrates the concept.
local MyClass = {}
MyClass.__index = {}

-- some variable that only exists in the class, but not objects
MyClass._SomeStaticVariable = 10

function MyClass.new(a, b)
    local self = {
        -- public variables that exists in the object
        Param1 = a,
        Param2 = b,
        
        -- private variable that exists in the object but not the class
        _PrivateVariable = (a + b) % 10,
    }
    
    return setmetatable(self, MyClass)
end

-- convert to a string for network sending
function MyClass:Serialize()
	-- you choose what to serialize, and no deep copying!
    return self.Param1 .. ";" .. self.Param2
end

function MyClass:Deserialize(str)
	-- convert back
	local aStr, bStr = str:match("(%d+);(%d+)")
	return MyClass.new(tonumber(aStr), tonumber(bStr))
end

function MyClass:DoSomething()
	print("Something")
end

return MyClass
1 Like

Thank you for the feedback! This will definitely come in handy. I’ll try and expand off of this example.