Metatables aren't being sent with the tables they are assigned to?

I’m making an RTS game, and am wanting to send data about units to every client. I’ve got an OOP setup with an overarching ‘Unit’ class that also has sub-classes assigned to it (e.g. a trooper class, where specific health and unit name is defined).

I ran into an issue where values that weren’t in the ‘main’ table (e.g. were retrieved using __index through a metatable) weren’t showing up on the client after they had been sent from the server.

To further clarify the issue, I created two quick scripts and a RemoteEvent:

Server

local meta = {name = "this works?"}
meta.__index = meta

local tab = {}
setmetatable(tab, meta)

print("Server:", tab.name)

wait(4)
game.ReplicatedStorage:WaitForChild("Connections"):WaitForChild("Test"):FireClient(
	game.Players:WaitForChild("EmeraldSlash"),
	tab
)

Client

game.ReplicatedStorage:WaitForChild("Connections"):WaitForChild("Test").OnClientEvent:Connect(function(tab)
	print("Client:", tab.name)
end)

and in the output I got:

Server: this works?
Client: nil

I also tested with a RemoteFunction.


TL;DR Metatables aren’t being sent from server to client in RemoteEvents/Functions

Does anyone know how I could solve this issue?

Thanks :slight_smile:

Metatables don’t get sent through RE/RF.
If they did, that’d mean functions would also need to be sendable, for example the __index, and this simply isn’t practically possible.

It requires to either clone the Lua State or serialize the function proto (for Lua functions only) back into bytecode to then send.

The first one poses a security and performance issue, since you’d probably need to replicate every change to it as to make it “workable”, and could be replicated by unsavory people.

The second works too, but things such as upvalues would crash the client if not replicated too, or would remain static if they did so.

So, ideally, not a good idea.

8 Likes

Rip I’ve based my entire game so far around this class system smh…

guess I’m gonna have to risk lag using module scripts inside objects along with some sort of external and global stat manager.

Edit: is there a way to loop through the metatables attached to a table? I’m thinking even if not, I might be able to make a custom system where it just adds values to a normal table.

1 Like

Common practice is to

  1. Give both the client and server access to the class and
  2. have a :Serialize member function and a .Deserialize static function

The :Serialize function will return all the info you need to reconstruct the object from scratch as either an array or dictionary. The .Deserialize function would then create a carbon-copy of the original instance of the class using :Serialized output.

If you were sending an instance of class from the server to the client, for example, you would:

  1. Serialize the instance on the server with Serialize, creating an array or dictionary. Let’s call this serialized.

  2. Sending serialized to the client via RemoteEvent.

  3. Calling .Deserialize(serialized) on the client, returning a carbon-copy of the original instance.

15 Likes