Информация Гайд Погружение в метавселенную! Разбор метаметодов, таблиц и их мета-версии!

movebx

Известный
Автор темы
47
143

Введение

Всем хай, здрасьте, привет и здарова. Темой сегодняшнего поста станут метаметоды. Метаметоды это такие функции, которые выполняются без вашего ведома и без явного вызова этих самых функций. Банальный пример - математические, побитовые операции, операции индексации (когда вы добавляете новое значение в таблицу или пытаетесь его получить оттуда) и прочие стандартные операторы Lua (#, tostring, ipairs, pairs и т.д.)
На самом деле, если разобраться в этой "технологии", то можно сделать свой код не только чище, но и проще в использовании.
Я уже писал маленькую статейку по поводу метатаблиц и их использовании для создания классов в Lua, вот ссылка -

Классы в Lua: перегрузка операторов и стандартных функций. C-подобные конструкторы.

Сразу хочу сказать, что рассматривать все метаметоды мы не будем, т.к. рядовому юзеру они навряд ли пригодятся в обычной разработке. Почитать про сборщик мусора, weak-tables и ipairs или pairs можно отдельно.
Так же некоторые метаметоды не поддерживаются в некоторых версиях Lua, пример тому - побитовые операции ( >>, <<, |, &, ^, ~ ) которые доступны только с версии Lua 5.4+. В моем случае я буду использовать Lua 5.4.2.
Показывать буду все так-же на примере вектора. Для начала практики сделаем себе базу в виде обычной таблицы:
Lua:
local vector = { } do
    
end
И так, начнем!

Метаметоды и операторы индексации.

__index

Индексация - процесс получения или установки значения в таблице. Самое банальный пример:
Lua:
local some_table = {
    x = 10,
    y = 20
}
print( some_table.x ) -- Пример получения значения из таблицы
some_table.y = 1337 -- Пример установки значения таблицы

Для того, чтобы контролировать получение значения из таблицы, мы можем использовать метаметод __index, который выглядит так:
Lua:
__index = function( self, key )
    
end
Где self - таблица, которая будет индексироваться, а key - значение, которое будет индексироваться.
(Так же стоит помнить о том, что значением __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
Но если мы попытаемся индексировать ключ y:
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 )
где 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, в свою очередь, вызывается тогда, когда мы пытаемся установить значение в таблицу, которого не существует. Выглядит метод так:
Lua:
__newindex = function( self, key, value )

end
где self - таблица, в которую будет установлено значение, key - ключ значения, а value - само значение.
__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 -- Ошибка
Решается это через метод rawset, который будет устанавливать значение напрямую:
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, а остальными - значения, которые будут переданы в конструктор
Lua:
local vector = { } do
    vector.new = function( self, x, y, z )
       
    end
end
Где self - ссылка на таблицу vector.
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, который выглядит так:
Lua:
__call = function( self, ... )

end
Где self - таблица, которая будет вызвана как функция, а ... - остальные аргументы. которые мы можем передавать.
В моей прошлой теме я использовал это для создания 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
Или вариант короче (т.к. __call совпадает с new):
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
} )
И теперь при попытке создать вектор с координатами 0, 0, 0 нам выбросит ошибку.

Математические метаметоды и операторы.

Математические метаметоды вызываются тогда, когда таблицу пытаются сложить с чем-либо.
Полный список всех математических операторов:
__unm - аналог унарного минуса ( - ), например ( -vector, -some_table )
__add - аналог сложения ( + )
__sub - аналог вычитания ( - )
__mul - аналог умножения ( * )
__div - аналог деления ( / )
__idiv - аналог целочисленного деления ( // )
__mod - аналог оператора модуля ( % )
__pow - аналог возведения в степень ( ^ )
Каждый из них выглядит так:
Lua:
__math_metamethod = function( self, value )

end
За исключением метода __unm, ему не нужен второй аргумент.

И так, для того, чтобы нам начать определять поведение математическим операторам и это все дело осталось +- красивым, сделаем следующее:
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
3. Заменим установку метатаблицы таблце-экземпляру в конструкторе на vector_mt:
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 сделала половину работы за нас, но об этом чуть позже.
Метаметоды, как и математические, выглядят так:
Lua:
__eq_metamethod = function( self, value )

end
Где self - таблица, с которой будет сравниваться, а value - то, с чем будет сравниваться

Добавим эти метаметоды в нашу таблицу 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 - аналог побитового сдвига вправо ( >> )

Опять же, повторяем те же действия, что и с математическими операторами и операторами сравнения:
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 - вызывается при попытке получить длину таблицы.

Опишем каждый из них:
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😉
 
Последнее редактирование:

tripple sixx

Участник
60
31
1171504.512.webp