Lua Metatables Guide
Introduction
Hello, I’m Shady, and I’m here with a new tutorial. At the bottom of this section, in the credits, you will find all the links to my other tutorials. Those should be your first priorities. I have prepared a tutorial that will help you explore metatables at an advanced level and set you on your way,The Lua programming language has gained popularity for its simple yet powerful syntax, making it a common choice for both game development and general-purpose programming. This guide will enable you to delve deep into the metatable feature of Lua and demonstrate how to apply object-oriented programming (OOP) concepts using metatables.
Metatables are used to customize the behavior of tables in Lua and manage complex data structures. In this guide, we will focus on the following topics:
What is a Metatable?: We will understand the concept of metatables and why they are used.
Creating Classes with Metatables: We will create a simple Vector class and explore operator overloading methods.
Inheritance: We will learn how to implement inheritance and override functions using metatables.
Complex Scenarios: We will create more complex structures, such as a character class, using metatables in real-world applications.
By the end of this guide, you will learn how to effectively utilize metatables in Lua, enhancing the functionality of your games or applications. So, let's step into the world of Lua metatables!
What is Metatable?
A metatable is a special table used to change the behavior of tables in Lua. With a metatable, you can inherit between tables and perform special operations.
Creating Metatable...
First, we need to create a metatable. We can define it as a simple table.
myMetatable = {}
Using Metatable in a Table
To assign a metatable to a table, we use the setmetatable function
myTable = {}
setmetatable(myTable, myMetatable)
Operator Overloading with Metatable
Metatables can also be used for operator overloading. For example, let's define the __add function for addition.
myMetatable.__add = function(t1, t2)
return t1.value + t2.value
end
Adjusting the Values of Tables
We can use the __index and __newindex methods in the metatable to set the values of tables.
myMetatable.__index = function(table, key)
return "Key not found: " .. key
end
myMetatable.__newindex = function(table, key, value)
rawset(table, key, value)
end
Example of Use
myTable.value = 5
local anotherTable = { value = 10 }
setmetatable(anotherTable, myMetatable)
local result = myTable + anotherTable
print(result) -- 15
Metatable and Mapping Functions
Metatables are used to provide functionality and inheritance relationships between tables in Lua. With special keys provided by the metatable, we can map between tables. Operator overloading functions like __index, __newindex, __add, and __sub allow us to change the behavior of tables.
An Example in Depth: Vector Class
Below, we will create a Vector class. This class will support addition, subtraction, and other vector operations.
-- Creating the vector metatable
Vector = {}
Vector.__index = Vector
-- Create a new vector
function Vector:new(x, y)
local vec = setmetatable({}, Vector)
vec.x = x or 0
vec.y = y or 0
return vec
end
-- Vector addition
function Vector:__add(other)
return Vector:new(self.x + other.x, self.y + other.y)
end
-- Vector subtraction
function Vector:__sub(other)
return Vector:new(self.x - other.x, self.y - other.y)
end
-- Calculate vector length
function Vector:length()
return math.sqrt(self.x^2 + self.y^2)
end
-- Usage example
local v1 = Vector:new(3, 4)
local v2 = Vector:new(1, 2)
local v3 = v1 + v2
print("Sum Vector:", v3.x, v3.y) -- 4, 6
print("Length of Vector 1:", v1:length()) -- 5
Metatables control the operations listed next. Each operation is identified by its corresponding name. The key for each operation is a string with its name prefixed by two underscores, '__'; for instance, the key for operation "add" is the string "__add". The semantics of these operations is better explained by a Lua function describing how the interpreter executes the operation.
add": the + operation.
The function getbinhandler below defines how Lua chooses a handler for a binary operation. First, Lua tries the first operand. If its type does not define a handler for the operation, then Lua tries the second operand.
function getbinhandler (op1, op2, event)
return metatable(op1)[event] or metatable(op2)[event]
end
By using this function, the behavior of the op1 + op2 is
function add_event (op1, op2)
local o1, o2 = tonumber(op1), tonumber(op2)
if o1 and o2 then -- both operands are numeric?
return o1 + o2 -- '+' here is the primitive 'add'
else -- at least one of the operands is not numeric
local h = getbinhandler(op1, op2, "__add")
if h then
-- call the handler with both operands
return (h(op1, op2))
else -- no handler available: default behavior
error(···)
end
end
end
"sub": the - operation. Behavior similar to the "add" operation.
"mul": the * operation. Behavior similar to the "add" operation.
"div": the / operation. Behavior similar to the "add" operation.
"mod": the % operation. Behavior similar to the "add" operation, with the operation o1 - floor(o1/o2)*o2 as the primitive operation.
"pow": the ^ (exponentiation) operation. Behavior similar to the "add" operation, with the function pow (from the C math library) as the primitive operation.
"unm": the unary - operation.
function unm_event (op)
local o = tonumber(op)
if o then -- operand is numeric?
return -o -- '-' here is the primitive 'unm'
else -- the operand is not numeric.
-- Try to get a handler from the operand
local h = metatable(op).__unm
if h then
-- call the handler with the operand
return (h(op))
else -- no handler available: default behavior
error(···)
end
end
end
"concat": the .. (concatenation) operation.
function concat_event (op1, op2)
if (type(op1) == "string" or type(op1) == "number") and
(type(op2) == "string" or type(op2) == "number") then
return op1 .. op2 -- primitive string concatenation
else
local h = getbinhandler(op1, op2, "__concat")
if h then
return (h(op1, op2))
else
error(···)
end
end
end
"newindex": The indexing assignment table[key] = value.
function settable_event (table, key, value)
local h
if type(table) == "table" then
local v = rawget(table, key)
if v ~= nil then rawset(table, key, value); return end
h = metatable(table).__newindex
if h == nil then rawset(table, key, value); return end
else
h = metatable(table).__newindex
if h == nil then
error(···)
end
end
if type(h) == "function" then
h(table, key,value) -- call the handler
else h[key] = value -- or repeat operation on it
end
end
"call": called when Lua calls a value.
function function_event (func, ...)
if type(func) == "function" then
return func(...) -- primitive call
else
local h = metatable(func).__call
if h then
return h(func, ...)
else
error(···)
end
end
end
Override and Inheritance
We can achieve inheritance using metatables in Lua. By creating subclasses and using the superclass's metatable, we can override some functions.
-- 2D Vector Metatable
Vector2D = setmetatable({}, Vector)
Vector2D.__index = Vector2D
-- Create a new 2D vector
function Vector2D:new(x, y)
local vec = Vector:new(x, y)
setmetatable(vec, Vector2D)
return vec
end
-- Rotate the 2D vector
function Vector2D:rotate(angle)
local cosA = math.cos(angle)
local sinA = math.sin(angle)
local newX = self.x * cosA - self.y * sinA
local newY = self.x * sinA + self.y * cosA
return Vector2D:new(newX, newY)
end
-- Usage example
local v2d = Vector2D:new(1, 0)
local v2dRotated = v2d:rotate(math.pi / 2)
print("Rotated Vector:", v2dRotated.x, v2dRotated.y) -- 0, 1
More Complex scripts
Metatables can be used to model complex data structures and behaviors. For example, we can create a class representing the attributes of a game character.
-- Character metatable
Character = {}
Character.__index = Character
function Character:new(name, health, power)
local char = setmetatable({}, Character)
char.name = name or "Unknown"
char.health = health or 100
char.power = power or 10
return char
end
function Character:attack(target)
target.health = target.health - self.power
print(self.name .. " attacked " .. target.name .. "!")
end
-- Usage example
local hero = Character:new("Hero", 150, 20)
local monster = Character:new("Monster", 80, 15)
hero:attack(monster)
print(monster.name .. " remaining health: " .. monster.health) -- 60
NOT : Metatables form one of the foundations of object-oriented programming in Lua, allowing you to create complex structures and functionalities. In this guide, you learned how to create classes using metatables, inherit from them, and perform operator overloading.
Metamethods:
metatables and metamethods offer powerful functionality that enhances the flexibility and behavior of tables. Metatables allow you to customize the behavior of tables, while metamethods are functions that define this behavior. This tutorial will explore the fundamentals of these concepts with fun and practical examples,Metamethods are special functions defined within a metatable that get triggered when specific operations occur.
Credits and Additional Resources
Official Lua Documentation;
Explore the official Lua documentation for comprehensive details on Lua features, including metatables.
https://www.lua.org/manual/5.4/manual.html#2.4
https://www.lua.org/manual/5.1/manual.html#2.8
Lua 5.1 Reference Manual
Lua 5.4 Reference Manual
Metatable Events
https://devdocs.io/lua~5.1/