All you need to know about Metatables and Metamethods

What does that last bit even mean? Does it mean that the table will be assigned a new value at said index?

The reason why they are locked is very obvious, as section VII. explains. They are locked so you can’t add metamethods to them, that might change the way properties are displayed, and many many other things that might break if the developer plays with stuff he’s not supposed to play with. They were disabled for safety from exploiters as well, but that ended up not being successful as they have getrawmetatable.

Second, good point! I didn’t actually cover that in the article, but to put it simply, if you set __newindex to a table, the new value that you wanted to set, will be set inside of the table __newindex is set to instead of the normal table.

1 Like

The string metatable and any other metatable by Roblox should be locked. All strings share the same metatable, even in different scripts. Including metatables for Instance, Vector3, etc. If Roblox didn’t lock these metatables your games wouldn’t function correctly because of malicious code. If you would like further explanation on this, PM me.

1 Like

There is no __gt, __ge, and __ne. > fires __lt with the order of arguments reversed (a>b = b<a), and >= fires __le with the order of arguments reverse (a>=b = b<=a). ~= simply reverses the result of == (a ~= b = not (a == b)).
Also might be worth mentioning __lt can be used to emulate __le, although this was removed in 5.4.
There are some special rules about the comparison operators, for __eq to fire both values must be of the same type and they must both have the same __eq metafield and the objects being compared can’t be raw equal. For __lt and __le to fire they must both be of the same type and they must both have the same metafield. (__lt and __le restrictions appear to have been removed at some point, not sure which version exactly, but they exist in 5.1, the version which Roblox uses)

C and C++ use 0 based indexing, so str[4] should give you the null character.

Technically, if the string has a v, it has weak values, and if the string has k, it has weak keys. It doesn’t have to be k, v, or kv.

local tbl = setmetatable({{}},{__mode="WEAK vALUES"})
print(tbl[1]) --> table: 0xXXX
collectgarbage()
print(tbl[1]) --> nil

Since this is a tutorial about metatables, here is a list of all metamethods

__index __newindex -- index operators
__mode __gc -- weak tables and garbage collection
__close -- new to 5.4 with to be closed variables
__name -- added in 5.3, used when building debug data and also used by tostring
__metatable -- sandboxing
__tostring -- overloading tostring
__pairs -- added in 5.2, overloads pairs
__ipairs -- added in 5.2 and deprecated in 5.3, overloads ipairs

__len __unm __add __sub __mul __div __idiv __mod __pow __concat __band __bor __bxor __bnot __shl __shr __eq __le __lt __call
-- operator overloading, __idiv was added in 5.3 for integer division
-- bitwise operator overloads added in 5.3 for bitwise operators
3 Likes

Thank you for the great additional info! The first note was already fixed, but I really lost focus about the “arrays start at 0”, I will fix that!

Also, didn’t know that lua doesn’t care what the string is, as long as it has a “v” in it, great to know!

I will also include a link to all of the metamethods the latest version of lua has.

Thanks again!

Metatables never replicate, ever. Not even if you have network ownership. Additionally, network ownership only covers physics, which is just the position and (rot/)velocity of parts.

1 Like

At first I was scared to approach this advance topic, and not to mention this post looked very long and intimidating. But it was so interesting and informative that it never got boring to read. There aren’t a lot of documentation on metas, so I’m glad this post exists. Aside from the few code mistakes, I think this is an underrated community resource. Thank you, it was very helpful! :pray:

3 Likes

This was highly informative and easy to follow!
I have always struggled to understand metatables and metamethods, and I am thankful for this tutorial because it has greatly enhanced my understanding of those concepts.

2 Likes

This is by far the easiest explanation of metatables I’ve seen. Been trying to properly understand this for ages and I didn’t realise it was quite that simple!

Now I understand why they’re magical. :astonished:
Thanks a bunch dude I have quite a good understanding of it now :smiley:

2 Likes

Hmm is there a way to automatically update the real object or something if a property is updated in the table. For example,

Dragon.Health = 50, and the health automiatically becomes 50, basically a metamethod to detect a change in a index. So basically like you know if you change the position property on a part the part automiatically moves

1 Like

Unfortunately no there are no metamethods that detect changes in indices. Best way to check for changes in an object is with Setters and Getters. Sure, you can use a while loop that constanly checks if a value inside of the table changed, but thats’s not cool. If you use a Setter, meaning a function that sets a property to a value

Dragon:SetHealth(50)

What you can do is, inside of the :SetHealth() function fire a bindable event and listen to it with .Event.

2 Likes

Hmm, ok. So basically properties dont change until a function is called. So if we had a table property with an index called transparency, the only way to update it with the object would be using a function. But do you have any idea how roblox does it, for example if we change a property of the part it shows in real time without a function calling

Ok just a couple of corrections, it’s more appropriate to say “a key called transparency”, an index is a number. And really it doesn’t mean you are forced to change properties using a function (a setter), you can support both facilities.

Dragon.Health = 50
Dragon:SetHealth(50)

I just mentionned that if you were implementing your own objects, it might be more convenient efficiency-wise to use the function instead, because that way you could just fire to the bindable event whenever the function is called, but with the object.property = value way, you would need a while loop that constantly checks if a property changed, that’s obviously not efficient. Roblox in thix context is not as lucky as you, since you’re the only one using the objects, you know that Setters are better ad you just use them for property changed events. Roblox is used by thousands of people, so you can’t force all of them to use a setter when the object.property = value way exists. Don’t take my words on this, but I assume Roblox just handles property changes the while loop way, it constantly checks for changes and if a change happened it fires the event. Remember that Roblox implements most of its things with C++, especially the cpu expensive stuff, like looping forever checking for property changes, so checking constantly for property changes isn’t as expensive as doing so in the lua side. Also Roblox starts checking for property changes, and really any other action that’s gonna fire an event, if an event for that action is connected. For example it doesn’t check constantly if a part touched another part and if so fire Touched event if one was connected. It starts checking for touched parts if a Touched event was connected to one of those parts. This also proves that :GetPropertyChangedSignal() is better than .Changed, if .Changed is connected, it checks constantly for any property change, :GetPropertyChangedSignal(property) will check constantly for the property you chose.

So tl;dr, just use setters because it’s more efficient and since you’re the only one using the objects you know that they’re better and you’d always use them.

2 Likes

I am finally beginning to understand metatables more, they always kind of confused me.

So if I’m correct, some functions like _index allows you to execute a function when a value doesn’t exist in a table or such?

Also curious to, in what ways could using metatables improve performance in games?

With this knowledge my coding skills already feel a whole lot more powerful, I’m still kind of figuring out where I could use these neat metatables though.

1 Like

Bookmarked! Now this is a useful post.

2 Likes

Yeah not really a word to take. Handling property changes in a loop is not efficient no matter what language you write it in. Roblox most certainly has some kind of internal system for queueing events or similar whenever something that is supposed to invoke them happens. .Changed won’t be looping to constantly change for property changes.

1 Like

I knew it. Who knows, maybe they have a proxy table setup?

Yes, you got the right idea. Some minor corrections: __index is the metamethod, it can be set to a function, and yes it invokes, run or fires or any other word you find suitable, when you index a key or index inside of a table that didn’t exist, meaning they are nil.

I’m glad you find yourself more powerful knowing this knowledge! The thing is, metatables aren’t really used for a whole lot when it comes to common use. There a lot of controversial topics talking about how it’s misused. Performance-wise, I wouldn’t argue that they make a difference, at the end they’re just a feature of lua, they weren’t original written to make your code more performant. I guess the only example where I would thing metatables make a significant change in performance is when dealing with OOP. Some people most of the time say “metatables are the only way to implement object-oriented programming in lua”, this is wrong, OOP is just lua’s table magic. The only thing that metatables do is make inheriting methods from the class easier. Instead of having to re-create the methods each time we create an object.

local class = {}

function class.new()
    local object = {name = "hi"}
    function object.SayHi() 

    end
    function object.SayBye()
 
    end
    return object
end

This is bad for performance, imagine creating a hundred of these objects, and having 2 functions for each object, that’s 200 functions created! With metatables, you simply keep the methods in the class, and you inherit them from the object, and no matter how many objects you make there is still 2 methods.

local class = {}
function class.SayHi() 

end
function class.SayBye()
 
end

function class.new()
    local object = {name = "hi"}
    setmetatable(object, class)
    return object
end

The thing is, as the topic that I linked by ScriptOn, don’t force yourself to use metatables just because they are cool and using them seems like a good idea. Just use them whenever you need to. That’s the thing.

When working on games, you’ll find youtself using metatables when dealing with modules sometimes.

Oh nice, honestly I think I could use metatables for player data and AI handling stuff.

I keep track of NPCs or custom humanoids in a table, figured out I can use metatables to turn my custom humanoid into some kind of Roblox object that I can call functions on.

And can also use metatables to return place-holder values in case player data is loading and doesn’t exist yet or such.

Little question, if I were to call object.SayHi() in your last code sample.
Would the first param be a table, second the key and the third one be a optional/custom?

It’s not really super clear yet to what values/params are passed when a function is called.

local class = {}
function class.printthis(tab, key, val) 
    print(tab, key, val)
end

function class.new()
    local object = {name = "NewObject"}
    setmetatable(object, class)
    return object
end

So if I now call object.printthis(), is tab the table, key the name/index of the key and val is the value of that key, does it print that and any param that comes after is like extra or? How would this work or how could I make that work?

1 Like

Sorry for the confusing code. Also I made a mistake of doing setmetatable(object, class) instead of setmetatable(object, {__index = class}).

From what I understand your question is, if extra values are passed beyond the 3 parameters, what happens? Well basically nothing! They are just ignored/discarded.

function class.printthis(tab, key, val)
     print(tab, key, val)
end

local t = {x =  5}

class.printthis(t, "x", 5, true)
--that true there doesn't actually cause any errors or mess with anything!