Advertisement
Guest User

Metatables

a guest
Feb 13th, 2018
481
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.20 KB | None | 0 0
  1. Original v3rm thread: https://v3rmillion.net/showthread.php?tid=404634&highlight=metatables
  2. -------
  3. (this may seem like a lot of reading, but i wrote this for people who want to learn what metatables are, and if you aren't willing to read a bit to learn how to do something then you won't get anywhere in life. also, it's only looks like a lot of reading. most of it is just code.)
  4. Hello everyone,
  5. It has quickly come to my attention that a lot of you striving Lua experts are not aware of metatables or do not know how they work / how to use them correctly and effectively. When I was a beginner, I thought metatables were some super complex mechanism that I would never master. Whenever I asked experienced Lua programmers what metatables were, they would answer saying something like "they aren't useful, you most likely won't use them ever" which is very much not true. That is why I am writing this; so that you can easily master this simple concept that many people believe is very difficult.
  6. At the end of this tutorial you should know what the following functions do: getmetatable, setmetatable, rawset, and rawget (and although this function isn't a Roblox function, I might as well tell you what getrawmetatable does too). Please drop a post if you haven't learned what any of these do by the end of this. Now let's start. First, I'm going to note a few things that will be very useful to know during this tutorial.
  7.  
  8. 1. We aren't learning about "metables", "mechatables", or even "materials"; were learning about metatables.
  9. Whenever I hear someone say getmetable or getmaterial rather than getmetatable, it annoys me so much. If you ever struggle to remember the name, split it up. Meta - table. The greek root 'meta' means beyond, and you know what a table is. This makes sense because metatables are like tables beyond another table.
  10.  
  11. 2. getrawmetatable only exists in exploits.
  12. The getrawmetatable function is basically a duplicate of the getmetatable function in the debug library (debug.getmetatable) (debug.getmetatable is not the same as normal getmetatable!). It gets the metatable without invoking the __metatable metamethod (which controls what is returned when you use getmetatable on the table/userdata. Roblox set this to "This metatable is locked" so that you can't get the metatable of stuff like instances or strings). The function has been recreated in exploits so that you can use it.
  13.  
  14. Alright, now that you know the basics, I'm going to explain what metatables actually are.
  15. You can think of metatables as a set of functions that controls what happens when you do something to a table. These functions are called metamethods. A lot of people get confused here, so try to remove the "meta" prefix from the term, and it may be easier to understand.
  16. Every metamethod starts with the prefix '__' (two underscores). This is to help distinguish the difference between normal variables and metamethods. There are many metamethods, such as __index and __newindex. These are the two most basic metamethods, and I will be explaining both.
  17. To understand these two metamethods, you should first learn what it means to index a table. Accessing data in a table is called indexing. For example, say you had a table (let's call it foo), and would like to get the first value in it. You would do foo[1] and that would get the first index in the table (foo). Some more examples of indexing are shown below:
  18. foo[8],
  19. foo.bar,
  20. foo['bar'],
  21. foo:bar()
  22.  
  23. Now that you know what indexing a table means, you will be able to understand what the __index metamethod is. Let's go back to what I said earlier. Metamethods are functions that control what happens when you do something to a table. So the __index metamethod controls what happens and what is returned when you index a table.
  24. The __index metamethod can either be a table or a function. When it is a table, that table will be indexed rather than the original table, and the value in the new table will be returned. When it is a function, it will be called with the arguments of (1) the table being indexed and (2) the key, and whatever is returned by the function will be returned as the value.
  25. However, this function will only be invoked if the value being indexed is nil.
  26.  
  27. The __newindex metamethod can only be a function (this is the same for all the other metamethods; __index is special). This controls what will happen when someone inserts a value into a table. The function is called with the arguments of (1) the table being inserted into, (2) the key that the value is being assigned to, and of course, (3) the new value.
  28. Just like the __index metamethod, it will only be invoked if the value you're inserting into the table was originally nil. For example:
  29. foo['bar'] = true -- will call the __newindex function because foo['bar'] didn't exist before yet.
  30. foo['bar'] = 999 -- will not call __newindex because the value already existed.
  31.  
  32. Now you've learned the basics of metatables. Congratulations! Now, I'm going to teach you how to actually use them. You can do this by using the setmetatable function.
  33. setmetatable takes two arguments. The first is the table you're setting the metatable of, and the second is the table containing all the metamethods. The function will return the new table with the metatable inside of it. Here's a bit of code that will print the key when a table is indexed:
  34. local mt = setmetatable({}, {
  35. __index = function(t, k) -- t being the table being indexed, k being the key
  36. print(k) -- print the key
  37. end
  38. })
  39. local v = mt.randomkey -- this will call the __index metamethod (because mt.randomkey is nil) and it will print "randomkey" because that is the key used.
  40.  
  41. However, this poses a small issue. If I did print(mt.randomkey) it'll print randomkey, because that's what I told it to do, but it also would print an empty string. This is because nothing is being returned from the function, and I am printing it. So what if you wanted it to always return "lol" if the value is nil? Well, you could do this:
  42. local mt = setmetatable({}, {
  43. __index = function(t, k)
  44. return 'lol'
  45. end
  46. })
  47. print(mt.somerandomthing)
  48.  
  49. This will print lol, because that is what's returned. You could think of this as like a pseudo value, because that's not actually what the value in the table is (it's usually nil, now we made it "lol")
  50. Another thing you may want to do is do something when the table is indexed (like print the key), but then return the value that it would originally return. Seems easy, right? You could just do return t[k], right?
  51. No. That would cause a stack overflow. This is because when you do t[k] inside the metamethod, it'll invoke the metamethod again (because you're indexing the table) which will again index it and invoke it again, and this will repeat forever, which will cause a crash. This is called recursion, and most of the time it's not a good thing.
  52. To fix this, we use the rawget function. Functions with the prefix "raw" usually mean they will do something to a table without invoking metamethods. There is also rawset which won't invoke __newindex, and rawlen which won't invoke the __len metamethod (which we won't talk about here but it basically is invoked when you use # on a table to get how many numerical keys it has).
  53. To put this in your code, you would do this:
  54. local mt = setmetatable({}, {
  55. __index = function(t, k)
  56. return rawget(t, k)
  57. end
  58. })
  59. print(mt.somerandomthing)
  60.  
  61. This will print nil, because t[k] is nil, and because of that you may think this is useless because it's nil no matter what (because __index doesn't work when the value is nil like I've stated many times). However, it's not useless, but it is mostly used outside of the metamethod so that you can get the original value without the metamethod effecting it. You'll probably find more use for the rawset function though, which takes the arguments the table, the key, and the value. This is good to use in the __newindex metamethod when you want to still change the value of something.
  62.  
  63. Congratulations! You've almost completed the tutorial! Now we move onto something really important. Making the __index and __newindex metamethod always be invoked, even when the value is nil.
  64. How is this possible, though? I thought those metamethods could only be invoked when the value was nil? Well, you thought right; and that's why they always will be nil. Whaaaaat???
  65. Let me explain. Basically, you'll one metatable (let's call it meta) and one table (let's call it realmeta). You'll set the __newindex of meta to change the value of realmeta rather than meta, and the __index to get the key provided from realmeta. This is called a proxy. Here's an example of it:
  66. local realmeta = {}
  67. local meta = setmetatable({}, {
  68. __index = function(t, k) -- t = table being indexed (meta), k = key
  69. print('indexed')
  70. return realmeta[k]
  71. end,
  72. __newindex = function(t, k, v) -- t = table that the value is being created in (meta), k = key, v = value
  73. realmeta[k] = v
  74. end
  75. })
  76. meta.randomkey = 55
  77. print(meta.randomkey) -- prints 'indexed' and 55
  78. print(realmeta.randomkey) -- 55
  79. print(rawget(meta, 'randomkey')) -- nil
  80.  
  81. You can see that when I'm setting meta.randomkey I'm actually setting realmeta.randomkey, and when I index meta.randomkey I'm actually indexing realmeta.randomkey. That's why when I print realmeta.randomkey it will print 55, and when I get meta.randomkey using rawget (will not invoke the __index metamethod) it will print nil because meta.randomkey doesn't actually exist
  82.  
  83. There is one last thing about metamethods that you may find frustrating. Metamethods cannot yield, meaning you can't use asynchronous functions inside of them like wait, HttpService.GetAsync, Players.GetUserIdFromNameAsync, etc. There are a lot more asynchronous functions, and they're usually identifiable by having "Async" in their name, but some don't. Asynchronous basically means it waits for the function to be completed before resuming the thread (function). You will know if you're yielding inside of a metamethod if you get an error that says something like "attempt to yield across C-call boundary".
  84.  
  85. Useful links
  86. http://wiki.roblox.com/index.php?title=Metamethods
  87. http://wiki.roblox.com/index.php?title=Metatable
  88.  
  89. Other releases
  90. https://v3rmillion.net/showthread.php?tid=401855 (Lua C API interpreter)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement