Шаг 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 и создаем новый проект.
7.Собираем, запускаем, на выходе должно получиться что-то вроде этого:
Ура, мы почти у цели! Осталось немного - из скрипта задать размеры окна
и текст в заголовке. Поехали.
Посмотрим на код, который создал нам визард. Нас интересует только
малая его часть:
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 закончен.
Файл примера можно взять здесь.
Файл скрипта можно взять здесь.
Назад