- 72
- 191
Введение
Всем хай, здрасьте, привет и здарова. Темой сегодняшнего поста станут метаметоды. Метаметоды это такие функции, которые выполняются без вашего ведома и без явного вызова этих самых функций. Банальный пример - математические, побитовые операции, операции индексации (когда вы добавляете новое значение в таблицу или пытаетесь его получить оттуда) и прочие стандартные операторы Lua (#, tostring, ipairs, pairs и т.д.)
На самом деле, если разобраться в этой "технологии", то можно сделать свой код не только чище, но и проще в использовании.
Я уже писал маленькую статейку по поводу метатаблиц и их использовании для создания классов в Lua, вот ссылка -
Классы в Lua: перегрузка операторов и стандартных функций. C-подобные конструкторы.
Сразу хочу сказать, что рассматривать все метаметоды мы не будем, т.к. рядовому юзеру они навряд ли пригодятся в обычной разработке. Почитать про сборщик мусора, weak-tables и ipairs или pairs можно отдельно.
Так же некоторые метаметоды не поддерживаются в некоторых версиях Lua, пример тому - побитовые операции ( >>, <<, |, &, ^, ~ ) которые доступны только с версии Lua 5.4+. В моем случае я буду использовать Lua 5.4.2.
Показывать буду все так-же на примере вектора. Для начала практики сделаем себе базу в виде обычной таблицы:
И так, начнем!
На самом деле, если разобраться в этой "технологии", то можно сделать свой код не только чище, но и проще в использовании.
Я уже писал маленькую статейку по поводу метатаблиц и их использовании для создания классов в Lua, вот ссылка -
Классы в Lua: перегрузка операторов и стандартных функций. C-подобные конструкторы.
Сразу хочу сказать, что рассматривать все метаметоды мы не будем, т.к. рядовому юзеру они навряд ли пригодятся в обычной разработке. Почитать про сборщик мусора, weak-tables и ipairs или pairs можно отдельно.
Так же некоторые метаметоды не поддерживаются в некоторых версиях Lua, пример тому - побитовые операции ( >>, <<, |, &, ^, ~ ) которые доступны только с версии Lua 5.4+. В моем случае я буду использовать Lua 5.4.2.
Показывать буду все так-же на примере вектора. Для начала практики сделаем себе базу в виде обычной таблицы:
Lua:
local vector = { } do
end
Метаметоды и операторы индексации.
__index
Индексация - процесс получения или установки значения в таблице. Самое банальный пример:
Для того, чтобы контролировать получение значения из таблицы, мы можем использовать метаметод __index, который выглядит так:
Где self - таблица, которая будет индексироваться, а key - значение, которое будет индексироваться.
(Так же стоит помнить о том, что значением __index может являться и сама таблица. При попытке получения значения из таблицы и его отсутствия там, Lua попытается обратиться к таблице, которая указана в качестве __index, и если не найдет это значение по указанному ключу - вернет nil)
Давай-те же установим нашей таблице vector метаметод __index и будем выводить значение, которое пытаются достать из таблицы:
Но, оказывается, не все так просто и есть пара подводных камней:
1. __index срабатывает только тогда, когда значения в таблице не существует.
То-есть если у нас есть какой то ключ key и мы пытаемся получить его из таблицы - __index не будет вызван.
Давайте наглядно посмотрим на это, добавив в таблицу vector ключ x со значением 10:
Но если мы попытаемся индексировать ключ y:
2. При возвращении значения, которого не существует получается stack overflow.
Почему это происходит? Дело в том, что ключа в таблице не существует и Lua пытается получить значение через метаметод __index, а т.к. он указан у нас в качестве функции, которая так же возвращает nil, Lua повторяет этот круг снова и снова, что приводит к переполнению стека и ошибке.
Решение довольно простое, но не самое очевидное. В Lua присутствует функция rawget, которая выглядит так:
где table - таблица, в которой будет искаться значение, а key - ключ этого значения.
rawget осуществляет прямой поиск в данной таблице, игнорируя __index, если значения по данному ключу не существует - возвращается nil.
Стоит чуть-чуть переписать наш код и мы лишимся данной ошибки:
И что же мы можем из этого получить? Ну, самое банальное было описано уже выше - получение информации о том, какие ключи индексируются в таблице, но можно пойти немного дальше. Напомню, что мы пишем vector, поэтому мы можем дать возможность индексировать нашу таблицу не только ключами x, y и z, а и числами (как обычный массив.)
Сделаем это так, для начала добавив в таблицу vector дополнительные значения y и z:
Так же маленький пример того, как __index может использоваться в качестве таблицы:
Lua:
local some_table = {
x = 10,
y = 20
}
print( some_table.x ) -- Пример получения значения из таблицы
some_table.y = 1337 -- Пример установки значения таблицы
Для того, чтобы контролировать получение значения из таблицы, мы можем использовать метаметод __index, который выглядит так:
Lua:
__index = function( self, key )
end
(Так же стоит помнить о том, что значением __index может являться и сама таблица. При попытке получения значения из таблицы и его отсутствия там, Lua попытается обратиться к таблице, которая указана в качестве __index, и если не найдет это значение по указанному ключу - вернет nil)
Давай-те же установим нашей таблице vector метаметод __index и будем выводить значение, которое пытаются достать из таблицы:
Lua:
local vector = { } do
setmetatable( vector, {
__index = function( self, key )
print( key )
return self[ key ]
end
} )
end
print( vector.x ) -- Возникла ошибка stack overflow
1. __index срабатывает только тогда, когда значения в таблице не существует.
То-есть если у нас есть какой то ключ key и мы пытаемся получить его из таблицы - __index не будет вызван.
Давайте наглядно посмотрим на это, добавив в таблицу vector ключ x со значением 10:
Lua:
local vector = { x = 10 } do
setmetatable( vector, {
__index = function( self, key )
print( key )
return self[ key ]
end
} )
end
print( vector.x ) -- Выведется только 10
Lua:
local vector = { x = 10 } do
setmetatable( vector, {
__index = function( self, key )
print( key )
return self[ key ]
end
} )
end
print( vector.y ) --Выведется сначала y, а после выпадет ошибка
2. При возвращении значения, которого не существует получается stack overflow.
Почему это происходит? Дело в том, что ключа в таблице не существует и Lua пытается получить значение через метаметод __index, а т.к. он указан у нас в качестве функции, которая так же возвращает nil, Lua повторяет этот круг снова и снова, что приводит к переполнению стека и ошибке.
Решение довольно простое, но не самое очевидное. В Lua присутствует функция rawget, которая выглядит так:
Lua:
rawget( table, key )
rawget осуществляет прямой поиск в данной таблице, игнорируя __index, если значения по данному ключу не существует - возвращается nil.
Стоит чуть-чуть переписать наш код и мы лишимся данной ошибки:
Lua:
local vector = { x = 10 } do
setmetatable( vector, {
__index = function( self, key )
print( key )
return rawget( self, key )
end
} )
end
print( vector.y ) -- Выведет y, nil
И что же мы можем из этого получить? Ну, самое банальное было описано уже выше - получение информации о том, какие ключи индексируются в таблице, но можно пойти немного дальше. Напомню, что мы пишем vector, поэтому мы можем дать возможность индексировать нашу таблицу не только ключами x, y и z, а и числами (как обычный массив.)
Сделаем это так, для начала добавив в таблицу vector дополнительные значения y и z:
Lua:
local vector = { x = 10, y = 2, z = 50 } do
setmetatable( vector, {
__index = function( self, key )
if ( key == 1 ) then -- Если индексируется первый элемент
return self.x -- возвращаем x
end
if ( key == 2 ) then -- Если индексируется второй элемент
return self.y -- возвращаем y
end
if ( key == 3 ) then -- Если индексируется третий элемент
return self.z -- возвращаем z
end
return rawget( self, key ) -- Иначе выводим значение по ключу
end
} )
end
print( vector[ 1 ] ) -- Выведет 10
print( vector[ 2 ] ) -- Выведет 2
Так же маленький пример того, как __index может использоваться в качестве таблицы:
Lua:
local vector_root = { xyz = 150 }
local vector = { x = 10, y = 2, z = 50 } do
setmetatable( vector, {
__index = vector_root
} )
end
print( vector.xyz ) -- Т.к. значения xyz в таблице vector не существует, Lua обратится к vector_root, т.к. он указан в качестве управляющей таблицы и найдет значение там. Выведет 150
Метаметоды и операторы индексации.
__newindex
__newindex, в свою очередь, вызывается тогда, когда мы пытаемся установить значение в таблицу, которого не существует. Выглядит метод так:
где self - таблица, в которую будет установлено значение, key - ключ значения, а value - само значение.
__newindex, в отличии от __index, не может являться таблицей.
И опять же
1. __newindex срабатывает только тогда, когда значения в таблице не существует.
2. при установке значения через оператор [ ] самой таблицы происходит переполнение стека.
Решается это через метод rawset, который будет устанавливать значение напрямую:
Где это использовать? Самое банальное - сделать таблицу read-only. Таким образом, добавлять новые значение в таблицу уже будет невозможно:
Lua:
__newindex = function( self, key, value )
end
__newindex, в отличии от __index, не может являться таблицей.
И опять же
1. __newindex срабатывает только тогда, когда значения в таблице не существует.
Lua:
local vector = { x = 10, y = 2, z = 50 } do
setmetatable( vector, {
__newindex = function( self, key, value )
print( key )
end
} )
end
vector.x = 50 --Ничего не выведется, а значение x изменится на 50
vector.xyz = 150 -- выведется xyz
2. при установке значения через оператор [ ] самой таблицы происходит переполнение стека.
Lua:
local vector = { x = 10, y = 2, z = 50 } do
setmetatable( vector, {
__newindex = function( self, key, value )
self[ key ] = value
end
} )
end
vector.x = 50 -- x теперь равен 50
vector.xyz = 150 -- Ошибка
Lua:
local vector = { x = 10, y = 2, z = 50 } do
setmetatable( vector, {
__newindex = function( self, key, value )
rawset( self, key, value )
end
} )
end
vector.xyz = 150 -- Ошибки теперь нет
print( vector.xyz ) -- Выведется 150
Где это использовать? Самое банальное - сделать таблицу read-only. Таким образом, добавлять новые значение в таблицу уже будет невозможно:
Lua:
local vector = { x = 10, y = 2, z = 50 } do
setmetatable( vector, {
__newindex = function( self, key, value )
error( "Attemt to add new value to vector table" )
end
} )
end
vector.xyz = 150 -- Ошибка Attemt to add new value to vector table
Создание простого класса. Базовый конструктор.
Основываясь на __index метаметоде, мы можем уже создать простой класс с конструктором.
Делается это так:
1. Создается новая функция в таблице vector (для конструкторов я предпочитаю использовать функцию с именем new), в которую мы первым параметром передаем self, а остальными - значения, которые будут переданы в конструктор
Где self - ссылка на таблицу vector.
2. Создаем новую таблицу и возвращаем ее мета-версию с __index = self
И теперь мы можем использовать эту функцию, которая будет создавать нам экземпляр класса vector.
Так же, можем создать пару простых методов, присущих вектору.
Т.к. __index у таблице-экземпляра указана внешняя таблица, то мы можем задать методы прямо в ней:
Делается это так:
1. Создается новая функция в таблице vector (для конструкторов я предпочитаю использовать функцию с именем new), в которую мы первым параметром передаем self, а остальными - значения, которые будут переданы в конструктор
Lua:
local vector = { } do
vector.new = function( self, x, y, z )
end
end
2. Создаем новую таблицу и возвращаем ее мета-версию с __index = self
Lua:
local vector = { } do
vector.new = function( self, x, y, z )
local vector = { }
vector.x = x or 0.0
vector.y = y or 0.0
vector.z = z or 0.0
return setmetatable( vector, {
__index = self
} )
end
end
И теперь мы можем использовать эту функцию, которая будет создавать нам экземпляр класса vector.
Lua:
local vector = { } do
vector.new = function( self, x, y, z )
local vector = { }
vector.x = x or 0.0
vector.y = y or 0.0
vector.z = z or 0.0
return setmetatable( vector, {
__index = self
} )
end
end
local vector1 = vector:new( 1.0, 2.0, 3.0 )
local vector2 = vector:new( 4.0, 5.0, 6.0 )
print( string.format( "vector1( %.1f, %.1f, %.1f )", vector1.x, vector1.y, vector1.z ) ) -- Выведется vector1( 1.0, 2.0, 3.0 )
print( string.format( "vector2( %.1f, %.1f, %.1f )", vector2.x, vector2.y, vector2.z ) ) -- Выведется vector2( 4.0, 5.0, 6.0 )
Так же, можем создать пару простых методов, присущих вектору.
Т.к. __index у таблице-экземпляра указана внешняя таблица, то мы можем задать методы прямо в ней:
Lua:
local vector = { } do
vector.new = function( self, x, y, z )
local vector = { }
vector.x = x or 0.0
vector.y = y or 0.0
vector.z = z or 0.0
return setmetatable( vector, {
__index = self
} )
end
vector.length = function( self )
return math.sqrt(
math.pow( self.x, 2 ) + math.pow( self.y, 2 ) + math.pow( self.z, 2 )
)
end
vector.dot = function( self, v_other )
return ( self.x * v_other.x ) + ( self.y * v_other.y ) + ( self.z * v_other.z )
end
vector.cross = function( self, v_other )
return vector:new(
self.y * v_other.z - self.z * v_other.y,
self.z * v_other.x - self.x * v_other.z,
self.x * v_other.y - self.y * v_other.z
)
end
end
local a = vector:new( 1, 2, 3 )
local b = vector:new( 3, 4, 5 )
print( a:length( ) ) -- Выведет 3.7416573867739
print( a:dot( b ) ) -- Выведет 26
Вызов таблиц как функций.
Таблицы можно вызвать как функции, тем самым можно задать поведение результату, который будет возвращен при вызове.
Для того, чтобы дать возможность вызывать таблицы как функции, мы можем использовать метаметод __call, который выглядит так:
Где self - таблица, которая будет вызвана как функция, а ... - остальные аргументы. которые мы можем передавать.
В моей прошлой теме я использовал это для создания C-подобного конструктора. Сделаем так же.
Для начала нам нужно задать уже внешней таблице vector метатаблицу, в который укажем __call:
Или вариант короче (т.к. __call совпадает с new):
И теперь, когда мы будем вызывать vector как функцию нам вернется тот же экземпляр, как и при вызове через метод new
Но, мы, например, можем позволить пользователю создавать только ненулевые векторы, сделаем это так же:
И теперь при попытке создать вектор с координатами 0, 0, 0 нам выбросит ошибку.
Для того, чтобы дать возможность вызывать таблицы как функции, мы можем использовать метаметод __call, который выглядит так:
Lua:
__call = function( self, ... )
end
В моей прошлой теме я использовал это для создания C-подобного конструктора. Сделаем так же.
Для начала нам нужно задать уже внешней таблице vector метатаблицу, в который укажем __call:
Lua:
local vector = { } do
vector.new = function( self, x, y, z )
local vector = { }
vector.x = x or 0.0
vector.y = y or 0.0
vector.z = z or 0.0
return setmetatable( vector, {
__index = self
} )
end
setmetatable( vector, {
__call = function( self, x, y, z )
return self:new( x, y, z )
end
} )
end
Lua:
__call = vector.new
И теперь, когда мы будем вызывать vector как функцию нам вернется тот же экземпляр, как и при вызове через метод new
Но, мы, например, можем позволить пользователю создавать только ненулевые векторы, сделаем это так же:
Lua:
setmetatable( vector, {
__call = function( self, x, y, z )
assert( x ~= 0 and y ~= 0 and z ~= 0,
"Attemt to create zero vector!" )
return self:new( x, y, z )
end
} )
Математические метаметоды и операторы.
Математические метаметоды вызываются тогда, когда таблицу пытаются сложить с чем-либо.
Полный список всех математических операторов:
__unm - аналог унарного минуса ( - ), например ( -vector, -some_table )
__add - аналог сложения ( + )
__sub - аналог вычитания ( - )
__mul - аналог умножения ( * )
__div - аналог деления ( / )
__idiv - аналог целочисленного деления ( // )
__mod - аналог оператора модуля ( % )
__pow - аналог возведения в степень ( ^ )
Каждый из них выглядит так:
За исключением метода __unm, ему не нужен второй аргумент.
И так, для того, чтобы нам начать определять поведение математическим операторам и это все дело осталось +- красивым, сделаем следующее:
1. Заведем новую таблицу, которую назовем vector_mt и опишем в ней все метаметоды, которые нам необходимы:
3. Заменим установку метатаблицы таблце-экземпляру в конструкторе на vector_mt:
И теперь остается это все дело только расписать. Применения оператору деления, умножения, возведения в степень я не нашел, поэтому просто опишу те же операции, только для вектора.
В свою очередь для оператора модуля я возьму функцию vector:cross()
И теперь можем протестировать все это дело:
Как видим все работает. Теперь все математические операции могут проводиться с таблицами.
Полный список всех математических операторов:
__unm - аналог унарного минуса ( - ), например ( -vector, -some_table )
__add - аналог сложения ( + )
__sub - аналог вычитания ( - )
__mul - аналог умножения ( * )
__div - аналог деления ( / )
__idiv - аналог целочисленного деления ( // )
__mod - аналог оператора модуля ( % )
__pow - аналог возведения в степень ( ^ )
Каждый из них выглядит так:
Lua:
__math_metamethod = function( self, value )
end
И так, для того, чтобы нам начать определять поведение математическим операторам и это все дело осталось +- красивым, сделаем следующее:
1. Заведем новую таблицу, которую назовем vector_mt и опишем в ней все метаметоды, которые нам необходимы:
Lua:
local vector_mt = { } do
vector_mt.__index = vector -- задаем управляющую таблицу
vector_mt.__unm = function( self ) -- унарный минус
end
vector_mt.__add = function( self, value ) -- оператор сложения ( + )
end
vector_mt.__sub = function( self, value ) -- оператор вычитания ( - )
end
vector_mt.__mul = function( self, value ) -- оператор умножения ( * )
end
vector_mt.__div = function( self, value ) -- оператор деления ( / )
end
vector_mt.__idiv = function( self, value ) -- оператор целочисленного деления ( // )
end
vector_mt.__mod = function( self, value ) -- оператор модуля ( % )
end
vector_mt.__pow = function( self, value ) -- оператор возведения в степень
end
end
Lua:
vector.new = function( self, x, y, z )
local vector = { }
vector.x = x or 0.0
vector.y = y or 0.0
vector.z = z or 0.0
return setmetatable( vector, vector_mt )
end
В свою очередь для оператора модуля я возьму функцию vector:cross()
Lua:
local vector_mt = { } do
vector_mt.__index = vector -- задаем управляющую таблицу
vector_mt.__unm = function( self ) -- унарный минус
return vector(
-self.x,
-self.y,
-self.z
)
end
vector_mt.__add = function( self, value ) -- оператор сложения ( + )
return vector(
self.x + value.x,
self.y + value.y,
self.z + value.z
)
end
vector_mt.__sub = function( self, value ) -- оператор вычитания ( - )
return vector(
self.x - value.x,
self.y - value.y,
self.z - value.z
)
end
vector_mt.__mul = function( self, value ) -- оператор умножения ( * )
return vector(
self.x * value.x,
self.y * value.y,
self.z * value.z
)
end
vector_mt.__div = function( self, value ) -- оператор деления ( / )
return vector(
self.x / value.x,
self.y / value.y,
self.z / value.z
)
end
vector_mt.__idiv = function( self, value ) -- оператор целочисленного деления ( // )
return vector(
self.x // value.x,
self.y // value.y,
self.z // value.z
)
end
vector_mt.__mod = function( self, value ) -- оператор модуля ( % )
return self:cross( value )
end
vector_mt.__pow = function( self, value ) -- оператор возведения в степень
return vector(
self.x ^ value.x,
self.y ^ value.y,
self.z ^ value.z
)
end
end
Lua:
local vec1 = vector( 1, 2, 3 )
local vec2 = vector( 4, 5, 6 )
local unm = -vec1
print( string.format( "unm = vector( %.1f, %.1f, %.1f )", unm.x, unm.y, unm.z ) ) --unm = vector( -1.0, -2.0, -3.0 )
local add = vec1 + vec2
print( string.format( "add = vector( %.1f, %.1f, %.1f )", add.x, add.y, add.z ) ) --add = vector( 5.0, 7.0, 9.0 )
local sub = vec1 - vec2
print( string.format( "sub = vector( %.1f, %.1f, %.1f )", sub.x, sub.y, sub.z ) ) --sub = vector( -3.0, -3.0, -3.0 )
local mul = vec1 * vec2
print( string.format( "mul = vector( %.1f, %.1f, %.1f )", mul.x, mul.y, mul.z ) ) --mul = vector( 4.0, 10.0, 18.0 )
local div = vec1 / vec2
print( string.format( "div = vector( %.1f, %.1f, %.1f )", div.x, div.y, div.z ) ) --div = vector( 0.2, 0.4, 0.5 )
local idiv = vec1 // vec2
print( string.format( "idiv = vector( %.1f, %.1f, %.1f )", idiv.x, idiv.y, idiv.z ) ) --idiv = vector( 0.0, 0.0, 0.0 )
local mod = vec1 % vec2
print( string.format( "mod = vector( %.1f, %.1f, %.1f )", mod.x, mod.y, mod.z ) ) --mod = vector( -3.0, 6.0, -7.0 )
local pow = vec1 ^ vec2
print( string.format( "pow = vector( %.1f, %.1f, %.1f )", pow.x, pow.y, pow.z ) ) --pow = vector( 1.0, 32.0, 729.0 )
Операторы и метаметоды сравнения.
Метаметоды сравнения вызываются, как ни странно, при сравнении двух таблиц.
__eq - аналог оператора ( == )
__lt - аналог оператора ( < )
__le - аналог оператора ( <= )
У некоторых мог возникнуть вопрос, а где противоположные данным операторам операторы? Все, на самом деле просто. Если в C++ нам надо было заботиться о каждом операторе индивидуально, то Lua сделала половину работы за нас, но об этом чуть позже.
Метаметоды, как и математические, выглядят так:
Где self - таблица, с которой будет сравниваться, а value - то, с чем будет сравниваться
Добавим эти метаметоды в нашу таблицу vector_mt. Для оператора сравнения ( == ) я буду использовать сравнение каждой координаты вектора, а для > и >= - сравнение их длин.
И теперь можем протестировать все это дело:
Помните я говорил о том, что Lua сделало половину работы за нас, так вот:
При попытке реверсировать операторы, результат, как не странно, будет правильный. Это происходит из за того, что Lua у себя под капотом проводит такую операцию:
not __lt()
not __le()
И получается вот так:
__eq - аналог оператора ( == )
__lt - аналог оператора ( < )
__le - аналог оператора ( <= )
У некоторых мог возникнуть вопрос, а где противоположные данным операторам операторы? Все, на самом деле просто. Если в C++ нам надо было заботиться о каждом операторе индивидуально, то Lua сделала половину работы за нас, но об этом чуть позже.
Метаметоды, как и математические, выглядят так:
Lua:
__eq_metamethod = function( self, value )
end
Добавим эти метаметоды в нашу таблицу vector_mt. Для оператора сравнения ( == ) я буду использовать сравнение каждой координаты вектора, а для > и >= - сравнение их длин.
Lua:
vector_mt.__eq = function( self, value ) -- оператор сравнения
return self.x == value.x and self.y == value.y and self.z == value.z
end
vector_mt.__lt = function( self, value ) -- оператор <
return self:length( ) < value:length( )
end
vector_mt.__le = function( self, value ) -- оператор <=
return self:length( ) <= value:length( )
end
И теперь можем протестировать все это дело:
Lua:
local eq = vec1 == vec2
print( "Equals?: ", eq ) --Equals?: false
local lt = vec1 < vec2
print( "Less than?: ", lt ) --Less than?: true
local le = vec1 <= vec2
print( "Less equal?: ", le ) --Less equal?: true
Помните я говорил о том, что Lua сделало половину работы за нас, так вот:
При попытке реверсировать операторы, результат, как не странно, будет правильный. Это происходит из за того, что Lua у себя под капотом проводит такую операцию:
not __lt()
not __le()
И получается вот так:
Lua:
local mt = vec1 > vec2
print( "More than?: ", mt ) --More than?: false
local me = vec1 >= vec2
print( "More equal?: ", me ) --More equal?: false
Побитовые операторы.
Побитовые операторы - относительно новая штука, которая пришла к нам с Lua 5.3.
__band - аналог побитового И ( & )
__bor - аналог побитового ИЛИ ( | )
__bxor - аналог побитового исключающего ИЛИ ( ^ ) (Его здесь не будет, потому что с ним сейчас проблемы)
__bnot - аналог побитового унарного исключающего НЕТ ( ~ )
__shl - аналог побитового сдвига влево ( << )
__shr - аналог побитового сдвига вправо ( >> )
Опять же, повторяем те же действия, что и с математическими операторами и операторами сравнения:
Тестируем:
__band - аналог побитового И ( & )
__bor - аналог побитового ИЛИ ( | )
__bxor - аналог побитового исключающего ИЛИ ( ^ ) (Его здесь не будет, потому что с ним сейчас проблемы)
__bnot - аналог побитового унарного исключающего НЕТ ( ~ )
__shl - аналог побитового сдвига влево ( << )
__shr - аналог побитового сдвига вправо ( >> )
Опять же, повторяем те же действия, что и с математическими операторами и операторами сравнения:
Lua:
vector_mt.__band = function( self, value ) -- оператор &
return vector(
self.x & value,
self.y & value,
self.z & value
)
end
vector_mt.__bor = function( self, value ) -- оператор |
return vector(
self.x | value,
self.y | value,
self.z | value
)
end
vector_mt.__bnot = function( self ) -- оператор ~
return vector(
~self.x,
~self.y,
~self.z
)
end
vector_mt.__shl = function( self, value ) -- оператор <<
return vector(
self.x << value,
self.y << value,
self.z << value
)
end
vector_mt.__shr = function( self, value ) -- оператор >>
return vector(
self.x >> value,
self.y >> value,
self.z >> value
)
end
Lua:
local band = vec1 & 123
print( string.format( "band = vector( %.1f, %.1f, %.1f )", band.x, band.y, band.z ) ) --band = vector( 1.0, 2.0, 3.0 )
local bor = vec1 | 123
print( string.format( "bor = vector( %.1f, %.1f, %.1f )", bor.x, bor.y, bor.z ) ) --bor = vector( 123.0, 123.0, 123.0 )
local bnot = ~vec1
print( string.format( "bnot = vector( %.1f, %.1f, %.1f )", bnot.x, bnot.y, bnot.z ) ) --bnot = vector( -2.0, -3.0, -4.0 )
local shl = vec1 << 123
print( string.format( "shl = vector( %.1f, %.1f, %.1f )", shl.x, shl.y, shl.z ) ) --shl = vector( 0.0, 0.0, 0.0 )
local shr = vec1 >> 123
print( string.format( "shr = vector( %.1f, %.1f, %.1f )", shr.x, shr.y, shr.z ) ) --shr = vector( 0.0, 0.0, 0.0 )
Прочие метаметоды.
Из неописанных мной методов остались:
__tostring - вызывается при приведении таблицы к строке
__metatable - вызывается при получении метатаблицы ( getmetatable() ). Его можно использовать для того, чтобы скрывать метатаблицу.
__len - вызывается при попытке получить длину таблицы.
Опишем каждый из них:
Теперь при приведении таблицы к строке, через tostring или, например, через print, нам выведется конструктор данного экземпляра.
При попытке получения метатаблицы нам вернется false (но сама она никуда не пропадет).
Так же я сделал так, что при попытке получить длину экземпляра вектора - вернется длина именно вектора, а не таблицы. Все просто
Тестируем:
Полный код:
Ну... и собственно все. Вот такой вот гайдик вышел. Применение этому - вагон и маленькая тележка, не стоит ограничиваться только тем же вектором. Применению, например, математическим операциям можно найти не только в математике. Маленький пример со строками:
"Hello " + "World!" - должно получиться Hello world
"FYP SASAL" - "A" - должно получиться "FYP SSL"
"Hello" == "Hello" - должно получиться true
И так далее.
Не скучайте, скоро будет одна очень интересная статейка. Будем реверсить сампик и дружить его функции и классы с Lua CAPI😉
__tostring - вызывается при приведении таблицы к строке
__metatable - вызывается при получении метатаблицы ( getmetatable() ). Его можно использовать для того, чтобы скрывать метатаблицу.
__len - вызывается при попытке получить длину таблицы.
Опишем каждый из них:
Lua:
vector_mt.__tostring = function( self )
return string.format( "vector( %.1f, %.1f, %.1f )", self.x, self.y, self.z )
end
vector_mt.__metatable = false
vector_mt.__len = function( self )
return self:length( )
end
Теперь при приведении таблицы к строке, через tostring или, например, через print, нам выведется конструктор данного экземпляра.
При попытке получения метатаблицы нам вернется false (но сама она никуда не пропадет).
Так же я сделал так, что при попытке получить длину экземпляра вектора - вернется длина именно вектора, а не таблицы. Все просто
Тестируем:
Lua:
print( vec1 ) --vector( 1.0, 2.0, 3.0 )
print( getmetatable( vec1 ) ) --false
print( #vec1 ) --3.7416573867739
Полный код:
Lua:
local vector = { } do
local vector_mt = { } do
vector_mt.__index = vector -- задаем управляющую таблицу
vector_mt.__unm = function( self ) -- унарный минус
return vector(
-self.x,
-self.y,
-self.z
)
end
vector_mt.__add = function( self, value ) -- оператор сложения ( + )
return vector(
self.x + value.x,
self.y + value.y,
self.z + value.z
)
end
vector_mt.__sub = function( self, value ) -- оператор вычитания ( - )
return vector(
self.x - value.x,
self.y - value.y,
self.z - value.z
)
end
vector_mt.__mul = function( self, value ) -- оператор умножения ( * )
return vector(
self.x * value.x,
self.y * value.y,
self.z * value.z
)
end
vector_mt.__div = function( self, value ) -- оператор деления ( / )
return vector(
self.x / value.x,
self.y / value.y,
self.z / value.z
)
end
vector_mt.__idiv = function( self, value ) -- оператор целочисленного деления ( // )
return vector(
self.x // value.x,
self.y // value.y,
self.z // value.z
)
end
vector_mt.__mod = function( self, value ) -- оператор модуля ( % )
return self:cross( value )
end
vector_mt.__pow = function( self, value ) -- оператор возведения в степень
return vector(
self.x ^ value.x,
self.y ^ value.y,
self.z ^ value.z
)
end
vector_mt.__eq = function( self, value ) -- оператор сравнения
return self.x == value.x and self.y == value.y and self.z == value.z
end
vector_mt.__lt = function( self, value ) -- оператор <
return self:length( ) < value:length( )
end
vector_mt.__le = function( self, value ) -- оператор <=
return self:length( ) <= value:length( )
end
vector_mt.__band = function( self, value ) -- оператор &
return vector(
self.x & value,
self.y & value,
self.z & value
)
end
vector_mt.__bor = function( self, value ) -- оператор |
return vector(
self.x | value,
self.y | value,
self.z | value
)
end
vector_mt.__bnot = function( self ) -- оператор ~
return vector(
~self.x,
~self.y,
~self.z
)
end
vector_mt.__shl = function( self, value ) -- оператор <<
return vector(
self.x << value,
self.y << value,
self.z << value
)
end
vector_mt.__shr = function( self, value ) -- оператор >>
return vector(
self.x >> value,
self.y >> value,
self.z >> value
)
end
vector_mt.__tostring = function( self )
return string.format( "vector( %.1f, %.1f, %.1f )", self.x, self.y, self.z )
end
vector_mt.__metatable = false
end
vector.new = function( self, x, y, z )
local vector = { }
vector.x = x or 0.0
vector.y = y or 0.0
vector.z = z or 0.0
return setmetatable( vector, vector_mt )
end
vector.length = function( self )
return math.sqrt(
math.pow( self.x, 2 ) + math.pow( self.y, 2 ) + math.pow( self.z, 2 )
)
end
vector.dot = function( self, v_other )
return ( self.x * v_other.x ) + ( self.y * v_other.y ) + ( self.z * v_other.z )
end
vector.cross = function( self, v_other )
return vector:new(
self.y * v_other.z - self.z * v_other.y,
self.z * v_other.x - self.x * v_other.z,
self.x * v_other.y - self.y * v_other.z
)
end
setmetatable( vector, {
__call = vector.new
} )
end
local vec1 = vector( 1, 2, 3 )
local vec2 = vector( 4, 5, 6 )
local unm = -vec1
print( string.format( "unm = vector( %.1f, %.1f, %.1f )", unm.x, unm.y, unm.z ) ) --unm = vector( -1.0, -2.0, -3.0 )
local add = vec1 + vec2
print( string.format( "add = vector( %.1f, %.1f, %.1f )", add.x, add.y, add.z ) ) --add = vector( 5.0, 7.0, 9.0 )
local sub = vec1 - vec2
print( string.format( "sub = vector( %.1f, %.1f, %.1f )", sub.x, sub.y, sub.z ) ) --sub = vector( -3.0, -3.0, -3.0 )
local mul = vec1 * vec2
print( string.format( "mul = vector( %.1f, %.1f, %.1f )", mul.x, mul.y, mul.z ) ) --mul = vector( 4.0, 10.0, 18.0 )
local div = vec1 / vec2
print( string.format( "div = vector( %.1f, %.1f, %.1f )", div.x, div.y, div.z ) ) --div = vector( 0.2, 0.4, 0.5 )
local idiv = vec1 // vec2
print( string.format( "idiv = vector( %.1f, %.1f, %.1f )", idiv.x, idiv.y, idiv.z ) ) --idiv = vector( 0.0, 0.0, 0.0 )
local mod = vec1 % vec2
print( string.format( "mod = vector( %.1f, %.1f, %.1f )", mod.x, mod.y, mod.z ) ) --mod = vector( -3.0, 6.0, -7.0 )
local eq = vec1 == vec2
print( "Equals?: ", eq ) --Equals?: false
local lt = vec1 < vec2
print( "Less than?: ", lt ) --Less than?: true
local le = vec1 <= vec2
print( "Less equal?: ", le ) --Less equal?: true
local mt = vec1 > vec2
print( "More than?: ", mt ) --More than?: false
local me = vec1 >= vec2
print( "More equal?: ", me ) --More equal?: false
local band = vec1 & 123
print( string.format( "band = vector( %.1f, %.1f, %.1f )", band.x, band.y, band.z ) ) --band = vector( 1.0, 2.0, 3.0 )
local bor = vec1 | 123
print( string.format( "bor = vector( %.1f, %.1f, %.1f )", bor.x, bor.y, bor.z ) ) --bor = vector( 123.0, 123.0, 123.0 )
local bnot = ~vec1
print( string.format( "bnot = vector( %.1f, %.1f, %.1f )", bnot.x, bnot.y, bnot.z ) ) --bnot = vector( -2.0, -3.0, -4.0 )
local shl = vec1 << 123
print( string.format( "shl = vector( %.1f, %.1f, %.1f )", shl.x, shl.y, shl.z ) ) --shl = vector( 0.0, 0.0, 0.0 )
local shr = vec1 >> 123
print( string.format( "shr = vector( %.1f, %.1f, %.1f )", shr.x, shr.y, shr.z ) ) --shr = vector( 0.0, 0.0, 0.0 )
print( vec1 ) --vector( 1.0, 2.0, 3.0 )
print( getmetatable( vec1 ) ) --false
Ну... и собственно все. Вот такой вот гайдик вышел. Применение этому - вагон и маленькая тележка, не стоит ограничиваться только тем же вектором. Применению, например, математическим операциям можно найти не только в математике. Маленький пример со строками:
"Hello " + "World!" - должно получиться Hello world
"FYP SASAL" - "A" - должно получиться "FYP SSL"
"Hello" == "Hello" - должно получиться true
И так далее.
Не скучайте, скоро будет одна очень интересная статейка. Будем реверсить сампик и дружить его функции и классы с Lua CAPI😉
Последнее редактирование: