Шаг 2

Итак, продолжаем разговор, как любил говорить Карлсон.
Сегодня мы рассмотрим такую архиважную вещь, как конфигурационный файл.
Уму непостижимо то количество кривых форматов и кривых парсеров, разбирающих форматы, написанные разработчиками обычно в самом начале проекта. Как обычно, все начинается с малого - из файла надо прочитать пару именованных переменных. Затем, по мере взросления проекта, формат становится все изощреннее и изощреннее, а парсер все глюкавее и глюкавее. Исправлять парсер никто не хочет, поскольку разработчик парсера скорее всего давно уволился, а другим разработчикам неохота глубоко вникать в эту помойку мыслевыделений, чаще всего плохо или вообще никак не документированную. В итоге каждая правка конфигурационного файла превращается в некий плохо сходящийся итерационный процесс, когда после каждого изменения нужно проверять, загружается он вообще или нет, и, если файл перестал загружаться, то в худшем случае придется лезть в исходники парсера и в каком-нибудь гигантском switch'е искать магические слова. Б-р-р!..
Пришло время положить этому конец! И поможет нам в этом, как вы уже догадались, Lua.
Приступим. Чтобы матерьял не казался сухим и пресным, мы сходим сразу по большому - будем писать какую нибудь трехмерную программу. Поможет нам в этом OpenGL. Чтобы не останавливаться на скучных вещах типа создания контекста рендеринга и прочей севершенно неинтересной ерунде, мы возьмем какую-нибудь кросс-платформенную обертку для взаимодействия OpenGL с окнами операционной системы. На сайте www.opengl.org можно выбрать подходящую. Я остановился на glfw. Эта библиотека имеет открытые исходники и достаточно проста. Плюс в IDE Code::Blocks есть уже готовый шаблон проекта для программы, использующей glfw.
Итак, сначала выполним подготовительные шаги.
1. Скачиваем glfw.
2. Распаковываем архив с glfw  куда-нибудь.
3. Убеждаемся, что переменная окружения PATH включает путь к .../CodeBlocks/bin.
4. Заходим в папку с распакованным glfw и в командной строке набираем compile make mgw.
5. Копируем полученные файлы libglfw.a и libglfwdll.a в папку .../CodeBlocks/lib, а файл glfw.h в папку .../CodeBlocks/include/GL.
6. Запускаем Code::Blocks и создаем новый проект.
new project

7.Собираем, запускаем, на выходе должно получиться что-то вроде этого:
glfw window

Ура, мы почти у цели! Осталось немного - из скрипта задать размеры окна и текст в заголовке. Поехали.
Посмотрим на код, который создал нам визард. Нас интересует только малая его часть:

int main()
{
    int        width, height;

    int        frame = 0;
    bool    running = true;


    glfwInit();

    if( !glfwOpenWindow( 512, 512, 0, 0, 0, 0, 0, 0, GLFW_WINDOW ) )
    {
        glfwTerminate();
        return 0;
    }

    glfwSetWindowTitle("GLFW Application");

...

Что здесь происходит? В начале вызывается функция glfwInit(), которую необходимо вызывать в самом начале программы, а затем вызывается функция glfwOpenWindow( 512, 512, 0, 0, 0, 0, 0, 0, GLFW_WINDOW ), которая собственно и создает окно размером 512х512 пикселей. Если все прошло успешно, то у созданного окна задается заголовок glfwSetWindowTitle("GLFW Application").
Дальше мы попадаем в бесконечный цикл, внутри которого происходит рисование, а также  проверка нажатых клавиш. Если нажата клавиша Esc или окно закрыли, то происходит выход из цикла и программа завершается.
Вот и все.
Теперь осталось немного - прикрутить к программе скрипт. Для начала неплохо определиться, что мы хотим от скрипта. На первом этапе мы хотим немного - управлять размером окна.
Пишем скрипт. Создаем текстовый файл и назовем его, например, config.lua.
Внутри этого файла напишем несколько строк:

windowWidth = 512
windowHeight = 512
windowTitle = '
GLFW Scripted Application!'


Сохраним файл. Все, скрипт готов.
Теперь добавляем поддержку скрипта в программу.

1. На первом шаге мы собрали Lua как .dll. Нам нужно наш проект слинковать с lua.dll. Для этого сначала копируем файлы Lua в папку проекта(это файлы lua.dll, liblua.a, lua.hpp, lua.h, lualib.h, lauxlib.h, luaconf.h). Затем идем в настройки проекта Project->Build Options->Linker и добавляем файл liblua.a

2. В main.cpp добавляем

#include "lua.hpp"

3. Собираем проект. Если мы ничего не забыли и ничего не напутали с путями, все должно собраться.
Ну, а теперь магия...
Перед инициализацией glfw пишем:

lua_State* L = lua_open();
luaL_openlibs(L);

Что мы сделали? Мы создали новый стек Lua и загрузили в него все стандартные библиотеки Lua. Что в себя включают стандартные библиотеки Lua? Это работа с таблицами, ввод/вывод, работа с функциямиоперационной системы(дата, время), работа со строками, математическая библиотека (фактически вся Си-шная <math>), дебаг и работа с модулями Lua. Что есть стек Lua? Это фактически база данных, в которой хранится состояние скрипта (все его переменные и функции). А почему это стек? А потому, что обращение к этой базе данных организовано по принципу стека - в стек заталкиваются аргументы, а затем производится  вызов функции, которая после выполнения своей работы также помещает результаты в стек, откуда эти результаты можно извлечь. Переходим от слов к делу.

4. Загружаем наш скрипт:

if(luaL_dofile(L, "config.lua"))
{
    const char* err = lua_tostring(L, -1);
    printf("%s\n", err);
}

Все функции Lua первым параметром принимают стек Lua. Поэтому в дальнейшем, говоря о передаваемых в функцию Lua параметрах, я буду говорить о параметрах, начиная со второго.
Функция luaL_loadfile загружает и выполняет файл скрипта. В случае неудачи функция возвращает отличное от нуля значение и помещает на вершину стека строку с сообщением об ошибке. Допустим, при загрузке скрипта произошла ошибка. Рассмотрим код обработчика данной ситуации.
Чтобы прочесть строку, находящуюся в стеке Lua воспользуемся функцией lua_tostring. Если в стеке по указанному индексу находится нечто, что может быть преобразовано в строку, а это строка или число, то Lua вернет указатель на строку, иначе вернет NULL. 
Пара слов об индексации стека Lua. Индексы можно задавать двумя способами - положительным или отрицательным целым числом.
Если число положительное, то индекс отсчитывается от основания стека. Нумерация стека начинается с 1 (не с нуля!). Соответственно, индекс самого нижнего элемента в стеке это 1.
Если число отрицательное, то индекс отсчитывается от вершины стека. Индекс -1 означает элемент, лежащий на вершине стека, -2 - элемент под ним, и т.д.
Теперь становится понятно магическое число -1 в вызове функции lua_tostring. Мы просто преобразоваваем в строку верхний элемент стека. Если файл скрипта отсутствует, то сообщение об ошибке будет выглядеть так (я подсмотрел значение переменной err в дебаггере во вкладке Watches): "cannot open config.lua. No such file or directory".

5.  Итак, скрипт загружен. Теперь нам нужно прочитать из него значения трех переменных: windowWidth, windowHeight и windowTitle.
Нет ничего проще! Пишем:

lua_getglobal(L, "windowWidth");

Функция lua_getglobal ищет глобальную переменную (а внашем скрипте все переменные глобальные)  с заданным именем и помещает результат на вершину стека. Аналогично тому, как мы читали сообщение об ошибке, читаем значение переменной:

width = (int)lua_tonumber(L, -1);

Функция lua_tonumber переводит значение по заданному индексу в число. Значение должно быть либо числом, либо строкой, которую можно привести к числу, иначе lua_tonumber возвращает 0.
С переменной windowHeight поступаем таким же образом:

lua_getglobal(L, "windowHeight");
height = (int)lua_tonumber(L, -1);

С переменной windowTitle поступаем следующим образом:

lua_getglobal(L, "windowTitle");
const char* title = lua_tostring(L, -1);

Вуаля! Мы все сделали! Теперь сложим все вместе, а также включим обработку всяких неприятностей.
Код до функции glfwInit() будет выглядеть примерно так:

int main()
{
    int width;
    int height;
    const char* title;
    int defaultWidth = 512;
    int defaultHeight = 512;
    const char* defaultTitle = "GLFW Not Scripted Application";

    int        frame = 0;
    bool    running = true;

    lua_State* L = lua_open();
    luaL_openlibs(L);

    if(luaL_dofile(L, "config.lua"))
    {
        const char* err = lua_tostring(L, -1);
        width = defaultWidth;
        height = defaultHeight;
        title = defaultTitle;
    }
    else
    {
        lua_getglobal(L, "windowWidth");
        width = (int)lua_tonumber(L, -1);
        if(0 == width)
            width = defaultWidth;

        lua_getglobal(L, "windowHeight");
        height = (int)lua_tonumber(L, -1);
        if(0 == height)
            height = defaultHeight;

        lua_getglobal(L, "windowTitle");
        title = lua_tostring(L, -1);
        if(NULL == title)
            title = defaultTitle;
    }

    glfwInit();

    if( !glfwOpenWindow( 512, 512, 0, 0, 0, 0, 0, 0, GLFW_WINDOW ) )
    {
        glfwTerminate();
        return 0;
    }

    glfwSetWindowTitle("GLFW Application");

...

На этом Шаг 2 закончен.
Файл примера можно взять здесь.
Файл скрипта можно взять здесь.

Назад


Hosted by uCoz