Чем так хорош Lua?
Когда-то я разрабатывал свою игру и задался вопросом: а какой формат данных лучше использовать?
Разработчики используют разные форматы: одни используют JSON, другие — XML, либо другие форматы данных. Ну а некоторые вообще хранят данные в .txt файлах или пишут свои парсеры. После рассмотрения различных форматов я остановился на Lua.
Lua можно использовать не только для игр, но и вообще для любых программ, которые используют данные, хранящиеся в других файлах.
Вот, что выделяет Lua на фоне других форматов:
- Lua легко использовать без дополнительных зависимостей (кроме одной библиотеки Lua и трёх .h файлов).
- В Lua файлах данные можно инициализировать с помощью математических выражений или функций, написанных на Lua. Например:
some_variable = math.sqrt(2) * 2 some_variable2 = 64 * 16 - 32
- Lua — очень быстрый язык, который к тому же не занимает много памяти.
- У Lua лицензия MIT, которая позволяет использовать этот язык как в бесплатных, так и в коммерческих проектах, причём без всякой возни с бумагами. Как написано на сайте: «просто скачайте и пользуйтесь».
- Lua комплируется практически везде, т.к. он написан на чистом C без использования дополнительных библиотек.
- Данные можно хранить и сортировать в приятном глазу виде. Их легко читать и модифицировать в любом текстовом редакторе.
Начнём с простого примера, а затем я перейду к реализации класса.
Пример
Допустим, есть файл Player.lua
player = { pos = { X = 20, Y = 30, }, filename = "res/images/player.png", HP = 20, -- а ещё можно комментарии добавлять }
С простым классом данные можно будет получать так:
LuaScript script("player.lua"); std::string filename = script.get<std::string>("player.filename"); int posX = script.get<std::string>("player.pos.X");
Внимание, чтобы код был понятен, рекомендуется прочесть информацию о том, как работает стек Lua и посмотреть на простейшие примеры.
Почитать можно здесь.
Начнём с создания класса:
#ifndef LUASCRIPT_H #define LUASCRIPT_H #include <string> #include <vector> #include <iostream> // Lua написан на C, поэтому нужно сообщить компилятору, чтобы он воспринимал хэдеры как код на C extern "C" { # include "lua.h" # include "lauxlib.h" # include "lualib.h" } class LuaScript { public: LuaScript(const std::string& filename); ~LuaScript(); void printError(const std::string& variableName, const std::string& reason); template<typename T> T get(const std::string& variableName) { // реализация функции последует позже в статье } // Возращаем 0 по умолчанию template<typename T> T lua_get(const std::string& variableName) { return 0; } // Эта функция используется в случае, если не удалось получить значение переменной и нужно вернуть какое-то // нулевое стандартное значение template<typename T> T lua_getdefault(const std::string& variableName) { return 0; } private: lua_State* L; }; #endif
Конструктор:
LuaScript::LuaScript(const std::string& filename) { L = luaL_newstate(); if (luaL_loadfile(L, filename.c_str()) || lua_pcall(L, 0, 0, 0)) { std::cout<<"Error: script not loaded ("<<filename<<")"<<std::endl; L = 0; } }
.
Создаём lua_State, в случае если файл не был найден, либо произошла какая-либо другая ошибка, выводим сообщение об этом.
Деструктор:
LuaScript::~LuaScript() { if(L) lua_close(L); }
Метод printError создан для того, чтобы выводить сообщения об ошибках:
void LuaScript::printError(const std::string& variableName, const std::string& reason) { std::cout<<"Error: can't get ["<<variableName<<"]. "<<reason<<std::endl; }
lua_getdefault используется для того, чтобы вернуть какое-либо нулевое значение, если произошла ошибка. И если для чисел можно вернуть ноль, то для строк, например, это не сработает, поэтому делаем специализацию шаблона (этот код будет в хэдере).
template<> inline std::string LuaScript::lua_getdefault<std::string>() { return "null"; }
А теперь напишем шаблонную функцию get.
Разберём алгоритм на примере. Пусть нужно получить переменную «player.pos.X» из файла Player.lua
Проходим циклом до первой точки, при этом добавляя прочитанные символы в переменную «var».
«player» — таблица, которая является глобальной, поэтому получаем её с помощью lua_getglobal.
«pos» и «X» — это уже данные, которые не являются глобальные, но их можно получить с помощью lua_getfield, т.к. сама таблица player находится в вершине стека. В конце алгоритма выполняется специфичная для типа данных функция, очищается стек и возвращается искомое значение, а в случае ошибки — вызывается функция lua_getdefault.
template <typename T> T get(const std::string& variableName) { if(!L) { printError(variableName, "Script is not loaded"); return lua_getdefault<T>(); } int level = 0; std::string var = ""; for(unsigned int i = 0; i < variableName.size(); i++) { if(variableName.at(i) == '.') { if(level == 0) { lua_getglobal(L, var.c_str()); } else { lua_getfield(L, -1, var.c_str()); } if(lua_isnil(L, -1)) { printError(variableName, var + " is not defined"); return lua_getdefault<T>(); } else { var = ""; level++; } } else { var += variableName.at(i); } } if(level == 0) { lua_getglobal(L, var.c_str()); } else { lua_getfield(L, -1, var.c_str()); } if(lua_isnil(L, -1)) { printError(variableName, var + " is not defined"); return lua_getdefault<T>(); } T result = lua_get<T>(variableName); lua_pop(L, level + 1); // pop all existing elements from stack return result; }
Осталось лишь добавить специализиации шаблонов(пример для некоторых типов данных):
template <> inline bool LuaScript::lua_get<bool>(const std::string& variableName) { return (bool)lua_toboolean(L, -1); } template <> inline float LuaScript::lua_get<float>(const std::string& variableName) { if(!lua_isnumber(L, -1)) { printError(variableName, "Not a number"); } return (float)lua_tonumber(L, -1); } template <> inline int LuaScript::lua_get<int>(const std::string& variableName) { if(!lua_isnumber(L, -1)) { printError(variableName, "Not a number"); } return (int)lua_tonumber(L, -1); } template <> inline std::string LuaScript::lua_get<std::string>(const std::string& variableName) { std::string s = "null"; if(lua_isstring(L, -1)) { s = std::string(lua_tostring(L, -1)); } else { printError(variableName, "Not a string"); } return s; }
На этом всё. Напоминаю, весь код в статье есть здесь. Там же можно найти пример использования класса.
Что дальше?
У Lua ещё много возможностей, которые я опишу во второй части статьи в ближайшем будущем. Например, получение массива данных неопределённой длины, а также получение списка ключей таблицы (например для таблицы Player из примера он был бы таким:[«pos», «filename», «HP»])
А ещё из Lua можно вызывать C++ функции, так же как и из C++ можно вызывать функции Lua, о чём я напишу в третьей части.
Удачного скриптинга!
ссылка на оригинал статьи http://habrahabr.ru/post/197300/
Добавить комментарий