--- title: Lua-модули brief: Lua-модули позволяют структурировать проект и создавать многократно используемый библиотечный код. Данное руководство объясняет, как это делается в Defold. --- # Lua-модули Lua-модули позволяют структурировать проект и создавать многократно используемый библиотечный код. Избежание дублирования в проектах является хорошей идеей. Defold позволяет использовать функциональность Lua-модулей, предоставляя возможность включать файлы скриптов в другие файлы скриптов. Это позволяет инкапсулировать функциональность (и данные) во внешний файл скрипта для повторного использования в скриптах игровых объектов и GUI-скриптах. ## Запрос Lua-файла Lua-код, хранящийся в файлах с окончанием ".lua" где-либо в структуре игрового проекта, может быть затребован в файлах скриптов и GUI-скриптов. Чтобы создать новый файл Lua-модуля, кликните ПКМ по папке, в которой хотите его создать, в представлении *Assets*, затем выберите New... ▸ Lua Module. Дайте файлу уникальное имя и кликните Ok: ![new file](images/modules/new_name.png) Предположим, в файл "main/anim.lua" добавлен следующий код: ```lua function direction_animation(direction, char) local d = "" if direction.x > 0 then d = "right" elseif direction.x < 0 then d = "left" elseif direction.y > 0 then d = "up" elseif direction.y < 0 then d = "down" end return hash(char .. "-" .. d) end ``` В таком случае любой скрипт может потребовать этот файл и использовать функцию: ```lua require "main.anim" function update(self, dt) -- обновить позицию, задать направление и т.д. ... -- установить анимацию local anim = direction_animation(self.dir, "player") if anim ~= self.current_anim then sprite.play_flipbook("#sprite", anim) self.current_anim = anim end end ``` Функция `require` загружает конкретный модуль. Она начинает с просмотра таблицы `package.loaded`, чтобы определить, загружен ли уже модуль. Если да, то `require` возвращает значение, хранящееся в `package.loaded[module_name]`. В противном случае он загружает и оценивает файл с помощью загрузчика. Синтаксис строки имени файла, передаваемой в `require`, немного своеобразен. Lua заменяет символы '.' в строке имени файла на разделители путей: '/' в macOS и Linux и '\\' в Windows. Стоит отметить, что использование глобальной области видимости для хранения состояния и определения функций, как мы делали выше, обычно является плохой идеей. В этом случае существует риск конфликта имен, раскрытия состояния модуля или введения связи между пользователями модуля. ## Модули Для инкапсуляции данных и функций Lua использует _модули_. Lua-модуль --- это обычная Lua-таблица, используемая для хранения функций и данных. Таблица объявляется локальной, чтобы не засорять глобальную область видимости: ```lua local M = {} -- private local message = "Hello world!" function M.hello() print(message) end return M ``` Теперь модуль можно использовать. Опять же, предпочтительнее присвоить его локальной переменной: ```lua local m = require "mymodule" m.hello() --> "Hello world!" ``` ## Горячая перезагрузка модулей Рассмотрим простой модуль: ```lua -- module.lua local M = {} -- создает новую таблицу в локальной области видимости M.value = 4711 return M ``` И пользователь модуля: ```lua local m = require "module" print(m.value) --> "4711" (даже если "module.lua" изменен и перезагружен на горячую) ``` При горячей перезагрузке файла модуля код снова выполняется, но с `m.value` ничего не происходит. Почему? Во-первых, таблица, созданная в "module.lua", создается в локальной области видимости, и пользователю возвращается _ссылка_ на эту таблицу. Перезагрузка "module.lua" снова оценивает код модуля, но при этом создается новая таблица в локальной области видимости вместо обновления таблицы, на которую ссылается `m`. Во-вторых, Lua кэширует требуемые файлы. Когда файл требуется в первый раз, он помещается в таблицу [`package.loaded`](/ref/package/#package.loaded), чтобы его можно было быстрее прочитать при последующих запросах. Можно заставить файл быть повторно считанным с диска, установив запись файла в значение nil: `package.loaded["my_module"] = nil`. Чтобы правильно выполнить горячую перезагрузку модуля, необходимо перезагрузить модуль, сбросить кэш, а затем перезагрузить все файлы, которые использует модуль. Это далеко не оптимальный вариант. Вместо этого можно рассмотреть обходной путь, который можно использовать _при разработке_: поместить таблицу модуля в глобальную область видимости и заставить `M` ссылаться на глобальную таблицу вместо создания новой таблицы при каждой оценке файла. Перезагрузка модуля изменит содержимое глобальной таблицы: ```lua --- module.lua -- Заменить на локальное M = {} после завершения uniquevariable12345 = uniquevariable12345 or {} local M = uniquevariable12345 M.value = 4711 return M ``` ## Модули и состояние Модули с состоянием хранят внутреннее состояние, которое является общим для всех пользователей модуля, и их можно сравнить с синглтонами: ```lua local M = {} -- Все пользователи модуля будут совместно использовать эту таблицу local state = {} function M.do_something(foobar) table.insert(state, foobar) end return M ``` С другой стороны, модуль без состояния не хранит никакого внутреннего состояния. Вместо этого он предоставляет механизм для экстернализации состояния в отдельную таблицу, локальную для пользователя модуля. Вот несколько различных способов реализовать это: Использование таблицы состояний : Возможно, самый простой подход заключается в использовании функции-конструктора, которая возвращает новую таблицу, содержащую только состояние. Состояние явно передается в модуль в качестве первого параметра каждой функции, которая манипулирует таблицей состояний. ```lua local M = {} function M.alter_state(the_state, v) the_state.value = the_state.value + v end function M.get_state(the_state) return the_state.value end function M.new(v) local state = { value = v } return state end return M ``` Use the module like this: ```lua local m = require "main.mymodule" local my_state = m.new(42) m.alter_state(my_state, 1) print(m.get_state(my_state)) --> 43 ``` Использование метатаблиц : Другой подход заключается в использовании функции-конструктора, которая при каждом вызове возвращает новую таблицу с состоянием и публичными функциями модуля: ```lua local M = {} function M:alter_state(v) -- self is added as first argument when using : notation self.value = self.value + v end function M:get_state() return self.value end function M.new(v) local state = { value = v } return setmetatable(state, { __index = M }) end return M ``` Use the module like this: ```lua local m = require "main.mymodule" local my_state = m.new(42) my_state:alter_state(1) -- "my_state" is added as first argument when using : notation print(my_state:get_state()) --> 43 ``` Использование замыканий : Третий способ --- вернуть замыкание, содержащее все состояния и функции. Нет необходимости передавать экземпляр в качестве аргумента (явно или неявно с помощью оператора двоеточия), как при использовании метатаблиц. Этот метод также несколько быстрее, чем использование метатаблиц, поскольку вызовы функций не должны проходить через метаметоды `__index`, но при этом каждое замыкание содержит свою собственную копию методов, вследствие чего потребление памяти выше. ```lua local M = {} function M.new(v) local state = { value = v } state.alter_state = function(v) state.value = state.value + v end state.get_state = function() return state.value end return state end return M ``` Используйте модуль следующим образом: ```lua local m = require "main.mymodule" local my_state = m.new(42) my_state.alter_state(1) print(my_state.get_state()) ```