All you need to know about Metatables and Metamethods

What I actually mean is, when a function is called, does it automatically already pass on which table and key you called the function upon?

I just do class.printthis() with nothing inbetween the ().

function class.printthis(tab, key, val)
     print(tab, key, val) --Will this print the table, key and value
     --even when nothing is passed through class.printthis()?
end

Does it automatically fill in the tab, key and val parameters?

Or would I have to add those myself? How would that work?

Of course you have to fill those yourself! If you’re looking for something that passes them for you, try __tostring, this method actually triggers when you pass a table (with a metatable with __tostring in it) print. And will pass you the table in the function __tostring is set to, and that way you could also yoink it’s key and value. That way you won’t even need to make a function to print this, it just gets printed using print.

local metatableOfObject = {__tostring = function(t) return t, t.key end}

Although this way you can’t know which key to return and also there may be occasions when you just want to tostring a table (because obviously __tostring fires for that too.

Honestly don’t do this and pass them on your own

Yea I kinda was trying to figure out how I could get a function to always know what table/key it is called from, it’s for a NPC module with custom humanoids.

Want to use metatables since they’re like custom objects (better performance instead of having 200 functions) and any script can require and use the module, but when a function like :moveto() is called,
Example:

customhumanoids[workspace.NPC]:moveto(position)

I somehow have to figure out so the metatable/function knows which npc I am calling it on.

With that, I have a bit of trouble figuring out how to do that, I re-readed some parts of the post to make sure I didn’t miss anything on that.

I see what you want. You can simply have :moveto() in the class, and inherit it the metatable way.

local class = {}

function class:moveto(position)
    --stuff
end

function class.new()
     local object = {some properties}
     setmetatable(object, {__index = class})
     return object
end

And to know which NPC you’re calling the method upon each time, you simply check for self. self would be the NPC you called the method on each time.

function class:moveto(position)
   self.Position = Position --or however you're going to do it
   --self would be the NPC each time
end

So self in this case, basically is the key I called the function upon?

Let’s say I do this…

local humanoids = {}

local humanoidobject = {}
humanoidobject.moveto = function(position)
    print("Function was called on ", self:GetFullName()) --Prints workspace.NPC??
    --Magic
end

humanoidobject.new = function(model)
    local newhuman = {
        health = {100, 100};
        movespeed = 16;
        root = model.rootpart;
    }

    setmetatable(newhuman, {__index = humanoidobject})

    humanoids[model] = newhuman
    return newhuman
end

Would…

humanoids[workspace.NPC].moveto(position)

now print the model I called it on? (As the model/instance is the key.)

(Code may have some errors since I almost never use metatables but now see that they can be very useful in some of my cases.)

@CoolShank8 @Autterfly

I actually I forgot about this, sorry for pointing it out only now. Instances are userdata, userdata can’t be indexed normally, unless it has a metatable with an __index, and also can’t have keys inside of them changed unless the metatable has a __newindex. I’m fairly sure that instances have metatables with an __index and __newindex, othrewise how could you index them or mutate properties. That way, whenever an object has a property changed, __newindex will run the function it’s bound to, and inside of that function Roblox probably informs the .Chagned event.

1 Like

Oh alright, but I have an other question, arent events just loops deep down, aren’t events just waiting for an action to trigger constantly

No. Just like starmaq said, when you set to an instance it will directly call __newindex. No loop, no constant checks. __newindex will then do all the work it needs and eventually queue or call the .Changed events. It would be extremely inefficient to check for things on loop.

2 Likes

Yah but Im taking like the touched event. I get the methamethod ones.

That’s also not a loop, unless you consider it part of the program loop. I assume Touched is queued to be fired after the physics step if any parts listening to it are reported to be touching.

1 Like

Nice tutorial.
But I don’t use metatables
also

nice wizard


it’s funny

4 Likes

Using functions from a table is kinda useful. Nice one :+1:!

1 Like

Great tutorial! Thank you for this awesome tutorial that helped me understand metatables more deeply.

1 Like

You a amazing at this. Thank you for writing this tutorial. :slightly_smiling_face:

1 Like

Alright, I have made a lot of classes and now I feel like I actually understand what metamethods almost fully instead of thinking of them like events. It also made me think about why an .IndexChanged meta method would be an paradox.

First of all we need to define the difference between an event and overwriting behavior and an callback.

First let’s talk about how events actually work. I would have never known this without making my own event library.

When you connect an event your basically adding this function to a table. When the :Fire() function is called all these functions are ran in seperate threads. For example when an part is touched, (roblox might do this by a loop for collision detection), and when a part is collided roblox might do this.

— LET ME ZOOM IN THE CODE THIS ISN’T OBVIOUSLY ALL OF COLLISION DETECTION JUST A SNIPPET

while true do
if Collision between part this and part that then
 Make both parts collide 
Part.Touched:Fire(PARAMETERS)
end
end

And any functions connected to it might be ran like this:

for i,v in pairs(Part.Touched.FunctionsToRun)
makeThread(v) --whatever running the function in a new thread
end

Now let’s explain what overwriting default behavior is. Events allow us to preserve default behavior while adding our own behavior when the event is fired.

For example before the .Touched event is fired roblox might make it so if a unanchored cancollide part is moving to an anchored canncollide part, they might make the part collide with it so it doesn’t go through(not saying they do they might, but this is a reasonable approach ) for physics.

Ok now lets talk about what overwriting behavior is.Events are kinda like semi independence, but when you overwrite default behavior it’s TOTAL INDEPENDENCE!!! This time we won’t be using Part.Touched:Fire() that makes no sense for overwriting default behavior. Instead let me propose a new thing

function Part.OnTouch()
Make parts collide
end

while true do
if Collision between part this and part that then
Part.OnTouch()
end
end

and when we do this

Part.OnTouch() = function() print “ToUCHED”

We are overwriting all behavior, so now the physics engine would actually collide the parts anymore it’ll only print touched. This is useful for total control.

Now finally calllbacks. I like to think of CallBacks as an mix between total control and no contro. (Event’s don’t give you control on what’s happening internally they just allow you to attach your code as part of the process when something happens).

function part.OnTouch()
return “LOL”
end

  while true do
    if Collision between part this and part that then
    Collide Parts
    local Result = Part.OnTouch()
    print(Result)
    end
    end

See here if we overwrite the .OnTouch function it’ll still collide the parts. Here we are using the returned result of the callback to do something and using the returned value unlike events. Sure it isn’t full control but it’s more control the events.

So finally what are methamethods actually doing!!!?? They are overwrting default behavior. When the lua Interpreter sees a metatable attached, and see’s a meta method corresponding to the action done to the table it does that instead of the default behavior.


https://gyazo.com/07548033edeef9ae27ed8a5516f666ff

Now how it does this can be done many ways. For example, it can do the way I did above

function Part.OnTouch()
Make parts collide
end

while true do
if Collision between part this and part that then
Part.OnTouch()
end
end

and when we do this

Part.OnTouch() = function() print “ToUCHED”

We are overwriting all behavior, so now the physics engine would actually collide the parts anymore it’ll only print touched. This is useful for total control.

Or it could be done using a different way such as using if statements. But the concept remains.

Now let’s talk about the infinite recursion paradox with an .IndexChanged event.

local Master = {
	__IndexChanged  = function(Table, Index, Value)
		Table[Index] = Value  -- we need to do this because ALL original behavior is overwrited so we need to manually set it
		-- UH OH now the statement above fires .INDEXCHANGED event again, oh no process repeats and repeats and repeats
		
		Table.OnNestedTableChanged() 
	end
		--- doesnt exist just an example how it would be if it did
}


local Table = {
	["HowManyNestedTablesYouHaveTable?"] = 'IDK'
}

Table["HowManyNestedTablesYouHaveTable?"] = 'actualy i think it is 5' -- ok INDEXCHANGED metamethod fires

Table.OnNestedTableChanged = function()
	print 'yayyyy'
end

setmetatable(Master, Table)

So this is why we use proxies.

With events this paradox wouldn’t come since when you do
Table.HowManyNestedTablesYouHaveTable["Cool] default behavior would be preserved and the event would just trigger when changing an index would happen internally in lua.

Ok to sum it up methamethods are overwriting what the lua interpeter actually does when an action is done to a table. I hope this helps cause I used to be thinking that metamethods were events but actually they aren’t based on the defintion I said above.

Oops I forgot that RawSETand those stuff existed. If IndexChanged was added then there might be RAWCHANGEINDEX, so the infinite recusion problem might not be a problem anymore.

3 Likes

Nitpick: It would error saying getting the length of the number value, not because you’re attempting to perform an arithmetic between number and string.

This isn’t correct. I think you’re mixing this up with the relational operators. Either way having the requirement of the same metamethod in order of some operator to work has been fully removed in Lua 5.3 (In Lua 5.2 __eq needs still needs to be the same metamethod in order of it to work).

I’d call it a metafield, it’s not considered a method

Actually it’ll print the function, not the message

Numbers, booleans, nils, functions and threads also share the same metatable and can have metatables, so are you implying these also have the same structure as a table?
Also this doesn’t apply to Lua 5.0 as metatable for all types (instead of just tables and userdatas) is actually a Lua 5.1 change.


Actually a < b is the same as b > a and a <= b is the same as b >= a.
Before Lua 4.0, it used to be that way but it has been changed because of not (NaN >= NaN) would return true.

1 Like

@Blockzez

Hey thanks for the feedback!
My explanation on __metatable is so stupid, I remember guessing how it works instead of actually checking an official documentation. I thought it ran a function, which in my opinion makes more sense. Thanks for letting me know!

Numbers, booleans, nils, functions and threads also share the same metatable and can have metatables, so are you implying these also have the same structure as a table?

Well, they don’t actually. Internally, all lua values are stored as a C union, containing all lua values. Numbers and booleans are stored as a simple primitive (int for the boolean, double for numbers), nil isn’t a member of the union because really it’s a global value that’s the same everywhere if you wanna call it that way. Now when it comes to tables, strings, threads, functions -all the stuff that’s garbage collected-, they’re stored together as a one member which is another union containing all of them (called a GCObject). Inside of the GCObject, each of the mentioned datatypes are a C struct which contains a bunch of stuff. You can see the Table struct has a member called metatable, while others don’t (strings don’t have the member too because remember they share the same metatable, so it’s handled somewhere else). When I say they have the same structure, I really mean the visual structure. Duck typing (unrelated but also google structural type systems if you’re interested) focuses on things that have the same memory layout, common method prototypes ect. Strings visualized are really just an array of characters, which is pretty similar to an array itself, they deserve to have the same features (or some of them) as an array, like having a metatable. You can get the length of an array, you can get the length of a string. You can index an array, you don’t really index a string in lua, but it sounds like something onviously feasible if it was a feature (strings can have a __index anyways).

1 Like

So just to be sure:

__index fires when a key is indexed?
__newindex fires when a new key is created or a key’s value is changed?

1 Like

__newindex fires when a new key is created or a key’s value is changed?

Not the key’s value is changed part! Only when a new key is created. But yes, the rest is correct.

so when a key’s value is changed, only __index fires?