|
|
(17 intermediate revisions by the same user not shown) |
Line 1: |
Line 1: |
| {| style="{{border-radius|4px}}; background-color: #ffdddd; border: dashed 2px #aa0000; border-top-color: #ff0000; border-left-color: #ff0000; margin: auto 10px; padding: 2px; display: block" | | {{User:Merlin11188/Templates/NoEdit}} |
| |{{ImageLink|image=ArrowSquare.png|link=User:Tomtomn00/Sandbox/NoEditTemplate}}
| |
| |<big>'''Do not edit!'''</big><br><small>The creator of this subpage does not want it to be edited without permission.</small>
| |
| |}
| |
| {{EmphasisBox|The metatables for [[Strings]] and all ROBLOX types are locked; however, in normal Lua (not RBX.lua) you can set the metatables of these objects using the debug library.[http://www.lua.org/manual/5.1/manual.html#5.9] |green|dark=yes}}
| |
| ==What is a Metatable?==
| |
|
| |
|
| Metatables allow tables to become more powerful than before. They are attached to data and contain values called Metamethods. Metamethods are fired when a certain action is used with the datum that it is attached to.
| |
| You may think that if you have code like this:
| |
|
| |
|
| <code lua>
| | {{Stub}} |
| local list = {1, 2}
| | {| |
| print(list[3])
| | |[[File:Home_Subpage.png|frame|A picture of the 'Home' tab on the submenu in 'My ROBLOX'.]] <br/> |
| </code> | | |} |
| | __TOC__ |
|
| |
|
| The code will search through the list for the third index in list, realize it's
| |
| not there, and return nil. That's totally wrong. What really happens is the code
| |
| will search through the list for the third index, realize it's not there, and then
| |
| try to see if there's a metatable attached to the table, returning nil if
| |
| there isn't one.
| |
|
| |
|
| ==setmetatable() and getmetatable()== | | ===Character and Notifications=== |
| The two primary functions for giving and finding a table's metatable are setmetatable() and getmetatable().
| | {| |
| <code lua>
| | |On the far left of the page there is a picture of [[My_Character|your character]]. Underneath, there is a link to your system notifications. |
| local x = {}
| | |[[File:Avatar_Notifications.png|frame|A picture of [[My_Character|your character]] with system notifications beneath it.]] |
| local metaTable = {} -- metaTables are tables, too!
| | |} |
| setmetatable(x, metaTable) -- Give x a metatable called metaTable!
| |
| print(getmetatable(x)) --> table: [hexadecimal memory address]
| |
| </code>
| |
|
| |
|
| ==Metatable Demonstration== | | ===Best Friends=== |
| | {| |
| | |Underneath of your avatar and notification box is your [[Friends#Best_Friends|best friends]] list. Here you can see what your best friends are doing (from their [[My_Home#Status_Update|shout box]]) and whether or not they're online. |
| | |[[File:Best_Friends.png|frame|This a list of your [[Friends#Best_Friends|best friends]] and their most recent shouts!]] |
| | |} |
|
| |
|
| {{Example|<code lua> | | ===Status Update=== |
| local list = {1, 2}
| | {| |
| print("In table List, key \"z\" is "..tostring(list.z)) --> In table List, key "z" is nil
| | |You can use this so that people who visit your profile can see what you're up to. |
| </code>
| | |[[File:Status Update.png|frame|This is the status update bar. People who have you as their [[Friends#Best_Friends|best friend]] will see it on their [[My_Home|home]]!]] |
| | |} |
|
| |
|
| Now, look at what happens with metatables:
| | ===Feed=== |
| | {| |
| | |Your feed is in the center of the page, just beneath your status update box. Your feed is a way to keep you updated with all of your groups. Every time someone uses the shout box in one of your [[groups]], you're updated here! |
| | |[[File:Feed bar.png|frame|This is for your feed—all of your [[groups]]' shouts go here.]] |
| | |} |
|
| |
|
| <code lua>
| | ===Recently Played Games=== |
| local list = {1, 2} -- A normal table
| | {| |
| local metatable = { -- A metatable
| | |On the far right is the 'Recently Played Games' box. Your most recently played games can be seen here. If you want a larger list, you can click the '''See More''' button. |
| __index = function(t, key)
| | |[[File:Recently_Played_Games.png|frame|This is a list of your most recently played [[game]]s.]] |
| rawset(t, key, 0) -- Set t[key] to 0
| | |} |
| return t[key] -- return t[key] (which is now 0)
| |
| end
| |
| }
| |
| setmetatable(list, metatable) -- Now list has the metatable metatable
| |
| print("In table List, key \"z\" is "..tostring(list.z)) --> In table List, key "z" is 0
| |
| </code>}}
| |
|
| |
|
| What happened there? <tt>list</tt> is a normal table, with nothing unusual about it. <tt>metatable</tt> is also a table, but it has something special: the <tt>__index</tt> metamethod. The <tt>__index</tt> metamethod is fired when <tt>t[key]</tt> is nil, or in this case, <tt>list.z</tt> is nil. Now, nothing would happen because the two tables (<tt>list</tt> and <tt>metatable</tt>) aren't linked. That's what the third line does: sets <tt>list</tt>'s metatable to <tt>metatable</tt>, which means that when <tt>list</tt> is indexed (<tt>__index</tt>) at an index that's nil (<tt>list.z</tt>), the function associated with <tt>__index</tt> in Metatable is run. The function at <tt>__index</tt> in <tt>metatable</tt> uses <tt>rawset</tt> to make a new value in the Table (or <tt>list</tt>). Then, that value is returned. So, <tt>list.z</tt> is set to 0, and then <tt>list.z</tt> is returned (which is 0).
| | ===Facebook Connect=== |
| | | {| |
| ==Metamethods== | | |On the far right, underneath of the 'Recently Played Games' box is the Facebook connect box. If you have a Facebook account, you can link it to your ROBLOX account! See [[connecting your account to Facebook]] for more info. |
| | | |[[File:FacebookConnect_Unconnected.png|frame|Facebook connect. You can use this to link your Facebook account to your [[Roblox|ROBLOX]] account! Your personal info will '''not''' be shared with other users!]] |
| Metamethods are the functions that are stored inside a metatable. They can go from
| | |} |
| calling a table, to adding a table, to even dividing tables as well. Here's a list
| |
| of metamethods that can be used:
| |
| | |
| *__index(Table, Index) — Fires when Table[Index] is nil.
| |
| *__newindex(Table, Index, Value) — Fires when Table[Index] = Value when Table[Index] is nil.
| |
| *__call(Table, arg) — Allows the table to be used as a function, arg is the arguments passed, Table(arg).
| |
| *__concat(Table, Object) — The .. concatenation operator.
| |
| *__unm(Table) — The unary – operator.
| |
| *__add(Table, Object) — The + addition operator.
| |
| *__sub(Table, Object) — The – subtraction operator.
| |
| *__mul(Table, Object) — The * mulitplication operator.
| |
| *__div(Table, Object) — The / division operator.
| |
| *__mod(Table, Object) — The % modulus operator.
| |
| *__pow(Table, Object) — The ^ exponentiation operator.
| |
| *__tostring(Table) — Fired when tostring is called on the table.
| |
| *__metatable — if present, locks the metatable so getmetatable will return this instead of the metatable and setmetatable will error. Non-function value.
| |
| *__eq(Table, Table2) — The == equal to operator˚
| |
| *__lt(Table, Table2) — The < less than operator˚
| |
| *__le(Table, Table2) — The <= operator˚
| |
| *__mode — Used in [[Weak Tables]], declaring whether the keys and/or values of a table are weak.
| |
| *__gc(Object) — Fired when the Object is garbage-collected.
| |
| *__len(Object) — Fired when the # operator is used on the Object. '''NOTE:''' Only userdatas actually respect the __len() metamethod, this is a bug in Lua 5.1
| |
| | |
| ˚ Requires two tables; does not work with a table and a number, string, etc. The tables must have the '''same''' metatable.
| |
| | |
| ==Using Metatables==
| |
| | |
| There are many ways to use metatables, for example the __unm metamethod (to make a table negative):
| |
| {{Example|<code lua>
| |
| local table1 = {10,11,12}
| |
| local metatable = {
| |
| __unm = function(t) -- __unm is for the unary operator -
| |
| local negatedTable = {} -- the table to return
| |
| for key, value in pairs(t) do
| |
| negatedTable[key] = -value
| |
| end
| |
| return negatedTable -- return the negative Table!
| |
| end
| |
| }
| |
| | |
| setmetatable(table1, metatable)
| |
| print(table.concat(-table1, "; ")) --> -10; -11; -12
| |
| </code>}}
| |
| | |
| Here's an interesting way to declare things using __index:
| |
| {{Example|<code lua>
| |
| local t = {}
| |
| local metatable = {
| |
| __index = {x = 1}
| |
| }
| |
|
| |
| setmetatable(t, metatable)
| |
| print(t.x) --> 1
| |
| </code>}}
| |
| | |
| __index was fired when x was accessed from the table. __index then defined x as 1 instead of nil; therefore, 1 was returned.
| |
| | |
| Now you can easily do that with a simple function, but there's a lot more where
| |
| that came from. Take this for example:
| |
| <code lua>
| |
| local table = {10, 20, 30}
| |
| print(table(5))
| |
| </code>
| |
| | |
| Now, obviously you can't call a table. That's just crazy, but (surprise, surprise!)
| |
| with metatables you can.
| |
| | |
| {{Example|<code lua>
| |
| local Table = {10, 20, 30}
| |
|
| |
| local metatable = {
| |
| __call = function(Table, param)
| |
| local sum = {}
| |
| for i, value in ipairs(Table) do
| |
| sum[i] = value + param -- Add the argument (5) to the value, then place it in the new table (t).
| |
| end
| |
| return unpack(sum) -- Return the individual table values
| |
| end
| |
| }
| |
| | |
| setmetatable(Table, metatable)
| |
| print(Table(5)) --> 15 25 35
| |
| </code>}}
| |
| | |
| You can do a lot more as well, such as adding tables!
| |
| {{Example|<code lua>
| |
| local table1 = {10, 11, 12}
| |
| local table2 = {13, 14, 15}
| |
| | |
| for k, v in pairs(table1 + table2) do
| |
| print(k, v)
| |
| end
| |
| </code>
| |
| | |
| This will error saying that you're attempting to perform arithmetic on a table. Let's try this with a metatable.
| |
| <code lua>
| |
| local table1 = {10, 11, 12}
| |
| local table2 = {13, 14, 15}
| |
| | |
| local metatable = {
| |
| __add = function(table1, table2)
| |
| local sum = {}
| |
| for key, value in pairs(table1) do
| |
| if table2[key] ~= nil then -- Does this key exist in that table?
| |
| sum[key] = value + table2[key]
| |
| else -- If not, add 0.
| |
| sum[key] = value
| |
| end
| |
| end
| |
|
| |
| -- Add all the keys in table2 that aren't in table 1
| |
| for key, value in pairs(table2) do
| |
| if sum[key] == nil then
| |
| sum[key] = value
| |
| end
| |
| end
| |
| return sum
| |
| end
| |
| }
| |
|
| |
| setmetatable(table1, metatable)
| |
| setmetatable(table2, metatable)
| |
| | |
| for k, v in pairs(table1 + table2) do
| |
| print(k, v)
| |
| end
| |
| | |
| --[[
| |
| Output:
| |
| | |
| 1 23
| |
| 2 25
| |
| 3 27
| |
| ]]
| |
| </code>}}
| |
| | |
| If the two tables have two different __add functions, then Lua will go to table1
| |
| first and if it doesn't have an __add function, then it'll go to the second one. That
| |
| means that you really only have to set the metatable of Table1 or Table2, but it's
| |
| nicer and more readable to set the metatable of both.
| |
| | |
| Here is one last example breaking away from using separate variables when it isn't necessary.
| |
| {{Example|<code lua>
| |
| local t = setmetatable({
| |
| 10, 20, 30
| |
| }, {
| |
| __call = function(a, b)
| |
| return table.concat(a, b .. ' ') .. b
| |
| end
| |
| })
| |
| print('Tables contains '..t(1)) --> Tables contains 101 201 301
| |
| </code>}}
| |
| | |
| ==rawset(), rawget(), rawequal()==
| |
| If you continue to play with metatables, you might encounter problems. Here's a primary example:
| |
| | |
| Let's say that we want to make a table error() whenever you try to access a nil value in it.
| |
| <code lua>
| |
| local planets = {Earth = "COOL!"}
| |
| local mt = {
| |
| __index = function(self, i)
| |
| return self[i] or error(i .. " is not a real Planet!") -- If self[i] is nil, then error!
| |
| end
| |
| }
| |
| | |
| setmetatable(planets, mt);
| |
| print(planets.Earth); --> COOL!
| |
| print(planets.Pluto); --> error: C stack overflow
| |
| </code>
| |
| | |
| So what does <tt>C stack overflow</tt> mean?
| |
| It means you've called the same function too many times inside of itself (or recursed too deeply). You called a function too many times too quickly! The normal limit is actually right around 20,000!
| |
| | |
| So what caused the <tt>__index()</tt> method to call itself ?
| |
| The answer: <tt>self[i]</tt>
| |
| | |
| So what can we do? How can we get a member of a table without calling it's <tt>__index()</tt> method?
| |
| With [[Function_Dump/Core_Functions#rawget_.28table.2C_index.29|rawget()]], of course!
| |
| | |
| | |
| {{EmphasisBox|[[Function_Dump/Core_Functions#rawget_.28table.2C_index.29|rawget()]], [[Function_Dump/Core_Functions#rawset_.28table.2C_index.2C_value.29|rawset()]], and [[Function_Dump/Core_Functions#rawequal_.28v1.2C_v2.29|rawequal()]] are very important when making custom metatables because they do not invoke a table's metamethods, so things like __index and __newindex aren't activated!}}
| |
| | |
| | |
| Let's make a small change to the code and try again!
| |
| <code lua>
| |
| local planets = {Earth = "COOL!"}
| |
| local mt = {
| |
| __index = function(self, i)
| |
| return rawget(self, i) or error(i .. " is not a real Planet!")
| |
| end
| |
| }
| |
|
| |
| setmetatable(planets, mt)
| |
| print(planets.Earth) --> COOL!
| |
| print(planets.Pluto) --> error: Pluto is not a real Planet!
| |
| </code>
| |
| | |
| | |
| | |
| ==Use Cases==
| |
| | |
| Now, I am well aware that you can do all of these as a simple function yourself,
| |
| but there's a lot more than what you think it can do. Let's try a simple program
| |
| that will memorize a number when a possibly laggy math problem is put into it.
| |
| | |
| For this one we will be using the __index metamethod just to make it simple:
| |
| | |
| {{Example|<code lua>
| |
| local Table = {}
| |
| | |
| local function mathProblem(num)
| |
| for i = 1, 20 do
| |
| num = math.floor(num * 10 + 65)
| |
| end
| |
| for i = 1, 10 do
| |
| num = num + i - 1
| |
| end
| |
| return num
| |
| end
| |
| | |
| local Metatable = {
| |
| __index = function (object, key)
| |
| local num = mathProblem(key)
| |
| object[key] = num
| |
| return num
| |
| end
| |
| }
| |
| | |
| local setmetatable(Table, Metatable)
| |
| | |
| print(Table[1]) -- Will be slow because it's the first time using this number.
| |
| print(Table[2]) -- will be slow because it's the first time using this number.
| |
| print(Table[1]) -- will be fast because it's just grabbing the number from the table.
| |
| </code>}}
| |
| | |
| ==Quiz==
| |
| {{Template:Quiz|
| |
| #Without looking at the list above, which metamethod allows you to add two tables?
| |
| #Without looking at the list above, which metamethod allows you to negate a table?
| |
| #If run, what will the following code result in?<code lua>
| |
| local quizTable = {"Hello", "Hi", "Bonjour"}
| |
| setmetatable(quizTable, {__index = function(parent, key)
| |
| rawset(parent, key, key * 2 .. tostring(rawget(parent, key/2)))
| |
| return key * 2 .. tostring(rawget(parent, key/2))
| |
| end})
| |
| | |
| print(quizTable[6])
| |
| </code>
| |
| | | |
| #__add. Pretty easy.
| |
| #__unm.
| |
| #12Bonjour
| |
| }}
| |
| | |
| ==See Also==
| |
| | |
| http://www.lua.org/manual/5.1/manual.html#2.8
| |
| | |
| http://www.lua.org/pil
| |
|
| |
|
| [[Category:Scripting_Tutorials]] | | <!-- |
| | {| class="wikitable" style="border-spacing: 0px; padding: 0px;" |
| | |- |
| | |[[File:Avatar + Notifications]] |
| | |[[File:Status_Update.png]] |
| | |[[File:Recently_Played_Games.png|287px]] |
| | |- |
| | |[[File:Best_Friends.png]] |
| | |<div style="top:0px;">[[File:Feed_bar.png]]</div> |
| | |[[File:FacebookConnect_Unconnected.png]] |
| | |}--> |