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

  • Автор темы deleted-user-453823
  • Дата начала
D

deleted-user-453823

Гость
Автор темы

Введение​

Всем хай, здрасьте, привет и здарова. Темой сегодняшнего поста станут метаметоды. Метаметоды это такие функции, которые выполняются без вашего ведома и без явного вызова этих самых функций. Банальный пример - математические, побитовые операции, операции индексации (когда вы добавляете новое значение в таблицу или пытаетесь его получить оттуда) и прочие стандартные операторы 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

Активный
167
73
1171504.512.webp