Lua создать файл если не существует
Данная статья — перевод моего туториала, который я изначально писал на английском. Однако этот перевод содержит дополнения и улучшения по сравнению с оригиналом.
Туториал не требует знания Lua, а вот C++ нужно знать на уровне чуть выше базового, но сложного кода здесь нет.
Когда-то я написал статью про использование Lua с C++ с помощью Lua C API. В то время, как написать простой враппер для Lua, поддерживающий простые переменные и функции, не составляет особого труда, написать враппер, который будет поддерживать более сложные вещи (функции, классы, исключения, пространства имён), уже затруднительно.
Врапперов для использования Lua и C++ написано довольно много. С многими из них можно ознакомиться здесь.
Я протестировал многие из них, и больше всего мне понравился LuaBridge. В LuaBridge есть многое: удобный интерфейс, exceptions, namespaces и ещё много всего.
Но начнём по порядку, зачем вообще использовать Lua c С++?
Зачем использовать Lua?
Конфигурационные файлы. Избавление от констант, магических чисел и некоторых define'ов
Данные вещи можно делать и с помощью простых текстовых файлов, но они не так удобны в обращении. Lua позволяет использовать таблицы, математические выражения, комментарии, условия, системные функции и пр. Для конфигурационных файлов это бывает очень полезно.
Например, можно хранить данные в таком виде:
Можно получать системные переменные:
Можно использовать математические выражения для задания параметров:
Скрипты, плагины, расширение функциональности программы
C++ может вызывать функции Lua, а Lua может вызывать функции C++. Это очень мощный функционал, позволяющий вынести часть кода в скрипты или позволить пользователям писать собственные функции, расширяющие функциональность программы. Я использую функции Lua для различных триггеров в игре, которую я разрабатываю. Это позволяет мне добавлять новые триггеры без рекомпиляции и создания новых функций и классов в C++. Очень удобно.
Немного о Lua. Lua — язык с лицензией MIT, которая позволяет использовать его как в некоммерческих, так и в коммерческих приложениях. Lua написан на C, поэтому Lua работает на большинстве ОС, что позволяет использовать Lua в кросс-платформенных приложениях без проблем.
Установка Lua и LuaBridge
Итак, приступим. Для начала скачайте Lua и LuaBridge
Добавьте include папку Lua и сам LuaBridge в Include Directories вашего проекта
Также добавьте lua52.lib в список библиотек для линковки.
Создайте файл script.lua со следующим содержанием:
Добавьте main.cpp (этот код лишь для проверки того, что всё работает, объяснение будет чуть ниже):
Скомпилируйте и запустите программу. Вы должны увидеть следующее:
LuaBridge works!
And here's our number:42
Примечание: если программа не компилируется и компилятор жалуется на ошибку “error C2065: ‘lua_State’: undeclared identifier” в файле LuaHelpers.h, то вам нужно сделать следующее:
1) Добавьте эти строки в начало файла LuaHelpers.h
2) Измените 460ую строку Stack.h с этого:
А теперь подробнее о том, как работает код.
Включаем все необходимые хэдеры:
Все функции и классы LuaBridge помещены в namespace luabridge, и чтобы не писать «luabridge» множество раз, я использую эту конструкцию (хотя её лучше помещать в те места, где используется сам LuaBridge)
Открываем наш скрипт. Для каждого скрипта не нужно создавать новый lua_State, можно использовать один lua_State для множества скриптов. При этом нужно учитывать коллизию переменных в глобальном нэймспейсе. Если в script1.lua и script2.lua будут объявлены переменные с одинаковыми именами, то могут возникнуть проблемы
Открываем основные библиотеки Lua(io, math, etc.) и вызываем основную часть скрипта (т.е. если в скрипте были прописаны действия в глобальном нэймспейсе, то они будут выполнены)
Создаём объект LuaRef, который может хранить себе всё, что может хранить переменная Lua: int, float, bool, string, table и т.д.
Преобразовать LuaRef в типы C++ легко:
Проверка и исправление ошибок
Но некоторые вещи могут пойти не так, и стоит производить проверку и обработку ошибок. Рассмотрим наиболее важные и часто встречающиеся ошибки
Что, если скрипт Lua не найден?
Что, если переменная не найдена?
Переменная может быть не объявлена, либо её значение — nil. Это легко проверить с помощью функции isNil()
Переменная не того типа, который мы ожидаем получить
Например, ожидается, что переменная имет тип string, тогда можно сделать такую проверку перед тем как делать каст:
Таблицы
Таблицы — это не просто массивы: таблицы — замечательная структура данных, которая позволяет хранить в них переменные Lua любого типа, другие таблицы и ставить ключи разных типов в соответствие значениям и переменным. Таблицы позволяют представлять и получать конфигурационные файлы в красивом и легкочитаемом виде.
Создайте script.lua с таким содержанием:
Код на C++, позволяющий получить данные из этого скрипта:
Можно также изменять содержимое таблицы:
Это не меняет значение в скрипте, а лишь значение, которое содержится в ходе выполнения программы. Т.е. происходит следующее:
Чтобы сохранить значение, нужно воспользоваться сериализацией таблиц(table serialization), но данный туториал не об этом.
Пусть теперь таблица выглядит так:
Как можно получить значение window.size.w?
Вот так:
Функции
Давайте напишем простую функции на C++
И напишем вот это в скрипте на Lua:
Затем мы регистрируем функцию в C++
Примечание 1: это нужно делать до вызова «luaL_dofile», иначе Lua попытается вызвать необъявленную функцию
Примечание 2: Функции на C++ и Lua могут иметь разные имена
Данный код зарегистрировал функцию в глобальном namespace Lua. Чтобы зарегистрировать его, например, в namespace «game», нужно написать следующий код:
Тогда функцию printMessage в скриптах нужно будет вызывать данным образом:
Пространства имён в Lua не имеют ничего общего с пространствами имён C++. Они скорее используются для логического объединения и удобства.
Теперь вызовем функцию Lua из C++
Вы должны увидеть следующее:
You can still call C++ functions from Lua functions!
Result:9
Разве не замечательно? Не нужно указывать LuaBridge сколько и каких аргументов у функции, и какие значения она возвращает.
Но есть одно ограничение: у одной функции Lua не может быть более 8 аргументов. Но это ограничение легко обойти, передав таблицу, как аргумент.
Если вы передаёте в функцию больше аргументов, чем требуется, LuaBridge молча проигнорирует их. Однако, если что-то пойдёт не так, то LuaBridge сгенерирует исключение LuaException. Не забудьте словить его! Поэтому рекомендуется окружать код блоками try/catch
Вот полный код примера с функциями:
Что? Есть ещё что-то?
Да. Есть ещё несколько замечательных вещей, о которых я напишу в последующих частях туториала: классы, создание объектов, срок жизни объектов… Много всего!
Также рекомендую прочитать этот dev log, в котором я рассказал о том, как использую скрипты в своей игре, практические примеры всегда полезны.
"С:\Quik1" , "C:\Quik2" и есть абсолютный путь. У меня тоже два Квика, но путает папки вовсе не Lua (там у меня как раз относительный путь типа F=io.open(getScriptPath().."//datafile.txt","r"), а сам Квик: при нажатии кнопки "добавить" при загрузке скриптов он открывает не папку "своего" Квика, а ту, из которой была последняя загрузка.
Цитата |
---|
Владимир написал: при нажатии кнопки "добавить" при загрузке скриптов он открывает не папку "своего" Квика, а ту, из которой была последняя загрузка. |
В этом конкретном случае квики путает винда. Квик просто открывает стандартный диалог, а тот сам помнит, кто что открывал последнее. Но помнит абы как, по имени программы без полного пути, вот и получается путаница. По-хорошему надо к диалогу прицеплять уникальный идентификатор , но квик этого не делает.
2.2.25 OnInit
Функция вызывается терминалом QUIK перед вызовом функции main(). В качестве параметра принимает значение полного пути к запускаемому скрипту.
STRING getScriptPath()
Функция возвращает путь, по которому находится запускаемый скрипт, без завершающего обратного слеша («\»). Например, C:\QuikFront\Scripts
Anton, Может, и Винда - она написана не менее коряво, чем Квик. Впрочем, фиг с ними обоими - у меня сейчас идёт финальная зачистка уже моего алгоритма - все "квиковские" штучки считаю отлаженными. Последняя правка была вчера: как известно, эта скотина теряет управление при остановке скрипта, поэтому файл результатов я записываю прямо в OnStop (благо запись не обращается к утилитам основного потока), а теперь там же стал ещё и убивать окно своей таблицы. Окно-то она убивает, "зато" влетает в режим этого дурацкого ожидания, так что теперь у меня OnStop возвращает 500, чтобы не мучился свои 5 секунд, и для юзера это (аварийное) завершение смотрится как нормальное. Комедия!
Цитата |
---|
Владимир написал: а теперь там же стал ещё и убивать окно своей таблицы |
Anton, И что бы это значило?
Anton, А нафига? Последний оператор в работе скрипта - пущай сами деструктурируют.
А ООП-религия у меня сложилась давно, ещё от Маккарти. И не всяким Страуструпам её поколебать!
Цитата |
---|
Владимир написал: при нажатии кнопки "добавить" при загрузке скриптов он открывает не папку "своего" Квика, а ту, из которой была последняя загрузка. |
Увидел.. от скобочек глаза заслезились ))
вообще qlua при завершении скрипта сама закрывает файлы. Видимо файл был закрыт до __gc
Похоже так оно и есть.
local run = true
local tid = nil
local file
--- Создание деструктора скрипта -----
gcrunner = <> --- Если gcrunner = <> (глобальная переменная), то __gc срабатывает только по завершению скрипта (обычно это и требуется).
--- . А если local gcrunner = <> (локальная переменная) то __gc срабатывает при при любом запуске collectgarbage (в том числе, и до завершения скрипта).
setmetatable(gcrunner, < __gc = function()
if tid then DestroyTable(tid) end
---- Выделенный ниже фрагмент срабатывает при запуске collectgarbage до завершения скрипта если gcrunner локальная переменная.
-- При запуске функции по заверщению скрипта, выделенный фрагмент не выполняется. Функция завершается на первом операторе
--- без его выполнения, но ошибка не выдается ??
file:write("__gc\n")
file:close()
---- Похоже при завершении скрипта файлы открепляются раньше, чем запускается финальная уборка мусора----
end >)
---------------------------------------------------
function OnInit(script_path)
file = io.open(script_path .. ".log", "w")
file:write("OnInit\n")
end
function main()
file:write("main\n")
tid = AllocTable()
AddColumn(tid, 1, '1', true, QTABLE_INT_TYPE, 1)
CreateWindow(tid)
-- collectgarbage () --- Если эту строку раскомментировать и при этом local gcrunner = <>, то при запуске collectgarbage () сработает __gc.
while run do sleep(300) end
file:write("main stopped\n")
end
function OnStop()
run = nil
file:write("OnStop\n")
end
Отформатированный код моего комментария:
local run = true
local tid = nil
local file
--- Создание деструктора скрипта -----
gcrunner = <> --- Если gcrunner = <> (глобальная переменная), то __gc срабатывает только по завершению скрипта (обычно это и требуется).
--- . А если local gcrunner = <> (локальная переменная) то __gc срабатывает при при любом запуске collectgarbage (в том числе, и до завершения скрипта).
setmetatable(gcrunner, < __gc = function()
if tid then DestroyTable(tid) end
---- Выделенный ниже фрагмент срабатывает при запуске collectgarbage до завершения скрипта если gcrunner локальная переменная.
-- При запуске функции по заверщению скрипта, выделенный фрагмент не выполняется. Функция завершается на первом операторе
--- без его выполнения, но ошибка не выдается ??
file:write("__gc\n")
file:close()
---- Похоже при завершении скрипта файлы открепляются раньше, чем запускается финальная уборка мусора----
end >)
---------------------------------------------------
function OnInit(script_path)
file = io.open(script_path .. ".log", "w")
file:write("OnInit\n")
end
function main()
file:write("main\n")
tid = AllocTable()
AddColumn(tid, 1, '1', true, QTABLE_INT_TYPE, 1)
CreateWindow(tid)
-- collectgarbage () --- Если эту строку раскомментировать и при этом local gcrunner = <>, то при запуске collectgarbage () сработает __gc.
while run do sleep(300) end
file:write("main stopped\n")
end
function OnStop()
run = nil
file:write("OnStop\n")
end
Цитата |
---|
TGB написал: А если local gcrunner = <> (локальная переменная) то __gc срабатывает при при любом запуске collectgarbage (в том числе, и до завершения скрипта). |
Есть такое. Значит, коллектор не считает объявление переменной ссылкой на нее. Значит, надо глобально ее объявлять или хотя бы иметь где-то ссылку на нее.
Библиотека ввода-вывода Lua используется для чтения и обработки файлов. Разделены на простой режим (такой же, как C) и полный режим.
- Простая модель имеет текущий входной файл и текущий выходной файл и предоставляет операции, связанные с этими файлами.
- Полная модель реализована с использованием внешнего дескриптора файла. Он определяет все файловые операции как методы обработки файлов в объектно-ориентированной форме.
Простой режим больше подходит для выполнения простых файловых операций. Но при выполнении некоторых расширенных операций с файлами простой режим оказывается неадекватным. Например, для таких операций, как одновременное чтение нескольких файлов, больше подходит полный режим.
Оператор операции с открытым файлом выглядит следующим образом:
Режим | описание |
---|---|
r | Чтобы открыть файл только для чтения, файл должен существовать. |
w | Откройте файл только для записи.Если файл существует, длина файла сбрасывается до 0, то есть содержимое файла исчезает. Если файл не существует, создайте файл. |
a | Откройте файл только для записи в режиме добавления. Если файл не существует, он будет создан.Если файл существует, записанные данные будут добавлены в конец файла, то есть исходное содержимое файла будет сохранено. (Символ EOF зарезервирован) |
r+ | Чтобы открыть файл в режиме чтения-записи, файл должен существовать. |
w+ | Откройте файл, доступный для чтения и записи.Если файл существует, длина файла сбрасывается до нуля, то есть содержимое файла исчезает. Если файл не существует, создайте файл. |
a+ | Подобен a, но этот файл доступен для чтения и записи. |
b | Двоичный режим, если файл является двоичным файлом, вы можете добавить b |
+ | Знак указывает, что файл можно читать или писать |
Простой режим
В простом режиме используется стандартный ввод-вывод или текущий входной файл и текущий выходной файл.
Ниже приведен код файла file.lua, файл, с которым нужно работать, - test.lua (если у вас его нет, вам нужно создать файл), код выглядит следующим образом:
Выполнив приведенный выше код, вы обнаружите, что первая строка информации в файле test.ua выводится, а комментарий lua добавляется к последней строке файла. Например, вот что я вывожу:
В приведенном выше примере мы использовали метод io. "X", где мы не брали параметры в io.read (), и параметр может быть одним из следующих:
Режим | описание |
---|---|
"*n" | Прочтите число и верните его. Пример: file.read ("* n") |
"*a" | Прочитать весь файл с текущей позиции. Пример: file.read ("* a") |
«* l» (по умолчанию) | Прочтите следующую строку и верните ноль в конце файла (EOF). Пример: file.read ("* l") |
number | Возвращает строку с указанным количеством символов или возвращает ноль, если EOF. Пример: file.read (5) |
Другие методы io:
- io.tmpfile():Возвращает временный дескриптор файла, файл открывается в режиме обновления и автоматически удаляется при завершении программы.
- io.type(file): Проверьте, является ли obj пригодным для использования дескриптором файла
- io.flush(): Записать все данные из буфера в файл
- io.lines(optional file name): Верните итеративную функцию, каждый вызов получит строку содержимого в файле, когда он достигнет конца файла, он вернет ноль, но файл не будет закрыт
Полный режим
Обычно нам нужно обрабатывать несколько файлов одновременно. Нам нужно использовать file: function_name вместо метода io.function_name. В следующем примере показано, как обрабатывать один и тот же файл одновременно:
Выполнив приведенный выше код, вы обнаружите, что первая строка информации в файле test.ua выводится, а комментарий lua добавляется к последней строке файла. Например, вот что я вывожу:
Параметры чтения соответствуют простому режиму.
- "set": начать с начала файла
- "cur": начать с текущей позиции [по умолчанию]
- "конец": начать с конца файла
- смещение: по умолчанию 0
Без параметров file: seek () возвращает текущую позицию, file: seek ("set") указывает на начало файла, file: seek ("end") находит до конца файла и возвращает размер файла.
file:flush(): Записать все данные из буфера в файл
io.lines(optional file name): Откройте указанное имя файла в режиме чтения и верните итеративную функцию. Каждый вызов будет получать строку содержимого в файле. Когда он достигнет конца файла, он вернет nil и автоматически закроет файл.
Если параметр отсутствует, io.lines () io.input (): lines (); считывает содержимое устройства ввода по умолчанию, но не закрывает файл при конец, например
В следующем примере метод seek используется для поиска 25-й позиции снизу файла и используется параметр * a метода чтения для чтения всего файла с текущей позиции (25-я позиция снизу).
Библиотека ввода-вывода предоставляет два разных стиля интерфейсов обработки файлов. Первый стиль использует неявные дескрипторы файлов; он обеспечивает операции для установки файлов ввода по умолчанию и файлов вывода по умолчанию, и все операции ввода и вывода направлены на эти файлы по умолчанию. Второй стиль использует явные дескрипторы файлов.
При использовании неявных дескрипторов файлов все операции выполняются таблицей io. Если используется явный дескриптор файла, io.open вернет дескриптор файла, и все операции обеспечиваются методами дескриптора файла.
Таблица io также предоставляет три предопределенных дескриптора файлов с тем же значением, что и в C: io.stdin io.stdout io.stderr. Библиотека ввода-вывода никогда не закроет эти файлы.
io:open(filename [, mode])
- ** "r": ** Режим только для чтения, который также является режимом открытия по умолчанию для существующих файлов.
- ** "w": ** Режим с возможностью записи, позволяющий изменять существующие файлы и создавать новые файлы (нечитаемые).
- ** "a": ** Режим добавления. Разрешено добавлять новое содержимое к существующему файлу, но не изменять исходное содержимое, а также можно создавать новые файлы.
- ** "r +": ** Открыть существующий файл в режиме чтения-записи.
- ** "w +": ** Если файл уже существует, удалите данные в файле; если файл не существует, создайте новый файл. Включен режим чтения и записи.
- ** "a +": ** Откройте существующий файл в удобном для чтения режиме добавления, если файл не существует, создайте новый файл.
Строка режима может содержать букву «b» в конце, что в некоторых системах открывает файл в двоичном режиме.
Когда файл не существует
Режимы «r», «r +», выдадут ошибку, эти два режима не составит труда создать автоматически.
режим "a", "a +", "w", "w +" создаст файлы
Когда файл существует
Исходный файл:
Для разных режимов вызовите операции чтения и записи отдельно
режим "r"
Файл доступен для чтения, но не для записи
режим "г +"
Содержимое файла сохраняется, новое содержимое вводится из заголовка файла
читаемый
режим "w"
Перезаписать содержимое файла вводом
При открытии файла в режиме «w» содержимое файла будет немедленно удалено, даже если содержимое не записано.
При чтении в режиме "w" вернуть nil
режим "w +"
Перезаписать содержимое файла вводом
При открытии файла в режиме "w +" содержимое файла будет немедленно удалено.
При чтении в режиме "w +" возвращается пустая строка.
режим "а"
Добавить режим записи
не читается
режим "а +"
Добавить режим записи
читаемый
io:input([file])
При вызове с именем файла откройте файл с этим именем (в текстовом режиме) и установите дескриптор файла в качестве входного файла по умолчанию. Если вы используете дескриптор файла для его вызова, просто установите дескриптор как входной файл по умолчанию. Если он вызывается без параметров, он возвращает текущий входной файл по умолчанию.
В случае ошибки функция выдает ошибку вместо возврата кода ошибки.
io:output([file])
При вызове его с именем файла откройте файл с этим именем (в текстовом режиме) и установите дескриптор файла как выходной файл по умолчанию. Если вы используете дескриптор файла для его вызова, просто установите дескриптор как выходной файл по умолчанию. Если он вызывается без параметров, он возвращает текущий выходной файл по умолчанию.
В случае ошибки функция выдает ошибку вместо возврата кода ошибки.
io.popen (prog [, mode])
Эта функция связана с системой, не все платформы ее предоставляют.
Запустите программу программы с отдельным процессом, и возвращенный дескриптор файла можно использовать для чтения данных из этой программы (если режим "r", это значение по умолчанию) или записи ввода в эту программу (когда режим "w" ).
io.tmpfile()
Возвращает дескриптор временного файла. Этот файл открывается в режиме обновления и будет автоматически удален по завершении программы.
io.type(obj)
Проверьте, является ли obj допустимым дескриптором файла. Если obj - дескриптор открытого файла, возвращается строка «файл». Если obj является дескриптором закрытого файла, возвращается строка «закрытый файл». Если obj не является дескриптором файла, вернитеnil 。
io.lines([filename])
Предоставьте итератор цикла для обхода файла, если имя файла указано, файл будет автоматически закрыт по окончании обхода; если используется файл по умолчанию, файл не будет автоматически закрыт после завершения обхода.
Примечание: Lua 5.3 имеет изменения, добавляющие количество режимов
file:lines()
Возвращает функцию итератора, каждый раз, когда вызывается итератор, из файла берется строка данных.В отличие от io.lines, эта функция не закрывает файл после завершения цикла.
Примечание: Lua 5.3 имеет изменения, добавляющие количество режимов
file:read(. )
Прочтите файл file, указанный формат определяет, что читать. Для каждого формата функция возвращает строку или число, соответствующие прочитанному символу. Если данные не могут быть прочитаны в этом формате, вернитеnil. (В этом последнем случае функция не будет читать последующий формат.) При вызове без формата она будет использовать формат по умолчанию для чтения следующей строки.
- *"n ": ** Считывает число из текущей позиции файла и возвращает nil, если позиция не является числом.
- *"l ": ** Прочитать текущую строку.
- *"a ": ** Прочитать все содержимое файла, начиная с текущей позиции указателя файла.
- number: Прочитать содержимое указанного количества байтов.
Примечание. В Lua 5.3 внесены изменения, убран знак * и добавлен формат «i» и «L».
io:read(. )
Эквивалентно io.input (): читать (···).
file:write(. )
Запишите значения параметров в файл по одному. Параметр должен быть строкой или числом.
В случае успеха функция возвращает файл. В противном случае вернитеnil Добавьте строку описания ошибки.
io:write(. )
Эквивалентно io.output (): write (···).
file:close()
Закройте файл. Обратите внимание, что файл будет автоматически закрыт, когда дескриптор будет обработан сборщиком мусора, но как долго это произойдет позже, непредсказуемо.
io:close()
Эквивалент файла: close (). Если файл не указан, выходной файл по умолчанию будет закрыт.
file:flush()
Сохраните записанные данные в файл
io:flush()
Эквивалентно io.output (): flush ().
file:seek([whence [, offset]])
Установите и получите позицию, рассчитанную на основе начала файла. Установленное положение определяется базовой точкой, заданной смещением и строкой откуда. Базовая точка может быть:
- ** "набор": ** Базовая точка - 0 (начало файла);
- ** "cur": ** Базовая точка - это текущая позиция;
- ** «конец»: ** Базовая точка - это конец файла;
Когда поиск завершается успешно, возвращается позиция файла, окончательно рассчитанная с начала файла. Когда поиск не удается, вернитесьnil Добавьте строку описания ошибки.
Значение по умолчанию для параметра whence - "cur", а значение смещения по умолчанию - 0. Следовательно, вызов file: seek () может вернуть текущую позицию файла, не изменяя ее; вызов file: seek ("set") установит позицию в начало файла (и вернет 0); вызывающий file: seek ("end") будет Установите позицию в конец файла и верните размер файла.
Изменение значения файла повлияет на операцию чтения и операцию записи открытия файла в режиме "r +".
Когда-то я разрабатывал свою игру и задался вопросом: а какой формат данных лучше использовать для конфигурационных файлов?
Ведь удобно, когда создаёшь какой-либо объект, задавать различные начальные параметры не в самом коде, а в отдельных файлах. Это позволяет изменять некоторые параметры объектов без рекомпиляции, да и вообще даёт возможность менять их людям далёким от программирования.
Разработчики используют разные форматы: одни используют JSON, другие — XML, либо другие форматы данных. Ну а некоторые вообще хранят данные в .txt файлах или пишут свои парсеры. После рассмотрения различных форматов я остановился на Lua.
Lua можно использовать не только для игр, но и вообще для любых программ, которые используют данные, хранящиеся в других файлах.
- Lua легко использовать без дополнительных зависимостей (кроме одной библиотеки Lua и трёх .h файлов).
- В Lua файлах данные можно инициализировать с помощью математических выражений или функций, написанных на Lua. Например:
Пример
Допустим, есть файл Player.lua
С простым классом данные можно будет получать так:
Внимание, чтобы код был понятен, рекомендуется прочесть информацию о том, как работает стек Lua и посмотреть на простейшие примеры.
Почитать можно здесь.
Начнём с создания класса:
lua_getdefault используется для того, чтобы вернуть какое-либо нулевое значение, если произошла ошибка. И если для чисел можно вернуть ноль, то для строк, например, это не сработает, поэтому делаем специализацию шаблона (этот код будет в хэдере).
А теперь напишем шаблонную функцию get.
Для начала напишем функцию lua_gettostack, которая заносит переменную на вершину стека
Разберём алгоритм на примере. Пусть нужно получить переменную «player.pos.X» из файла Player.lua
Проходим циклом до первой точки, при этом добавляя прочитанные символы в переменную «var».
«player» — таблица, которая является глобальной, поэтому получаем её с помощью lua_getglobal.
«pos» и «X» — это уже данные, которые не являются глобальные, но их можно получить с помощью lua_getfield, т.к. сама таблица player находится в вершине стека.
Затем уже в шаблонной функции get выполняется специфичная для типа данных функция, очищается стек и возвращается искомое значение, а в случае ошибки — вызывается функция lua_getdefault.
Возвращаемся к методу get:
Осталось лишь добавить специализиации шаблонов(пример для некоторых типов данных):
На этом всё. Напоминаю, весь код в статье есть здесь. Там же можно найти пример использования класса.
Что дальше?
У Lua ещё много возможностей, которые я опишу во второй части статьи в ближайшем будущем. Например, получение массива данных неопределённой длины, а также получение списка ключей таблицы (например для таблицы Player из примера он был бы таким:[«pos», «filename», «HP»])
А ещё из Lua можно вызывать C++ функции, так же как и из C++ можно вызывать функции Lua, о чём я напишу в третьей части.
Удачного скриптинга!
Читайте также: