...

суббота, 12 октября 2013 г.

[Из песочницы] Использование Lua и C++ для обработки и хранения данных

Код статьи можно посмотреть здесь.

Чем так хорош Lua?



Когда-то я разрабатывал свою игру и задался вопросом: а какой формат данных лучше использовать?

Разработчики используют разные форматы: одни используют JSON, другие — XML, либо другие форматы данных. Ну а некоторые вообще хранят данные в .txt файлах или пишут свои парсеры. После рассмотрения различных форматов я остановился на Lua.

Lua можно использовать не только для игр, но и вообще для любых программ, которые используют данные, хранящиеся в других файлах.


Вот, что выделяет Lua на фоне других форматов:




Начнём с простого примера, а затем я перейду к реализации класса.


Пример



Допустим, есть файл 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, о чём я напишу в третьей части.

Удачного скриптинга!

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Комментариев нет:

Отправить комментарий