29 января 2010

Lua + Delphi, компиляция

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

И сразу в бой... скажу, что понять, что такое скрипт довольно просто, достаточно прочесть хоть какую-то информацию об этом (1, 2, 3). а вот понять, зачем это необходимо для использования - гораздо труднее. до сих пор идут споры о применении скриптов. наверно, это будет длиться вечно. многие говорят, что это уже почти стандарт и уйти от скриптов будет сложно, многие утверждают, что скрипты медленно выполняются и просто жизненно необходимо заменить скрипты на использование dll. в общем - это кому как нравится. но надо осознавать, что построение вашего приложения и использование инструментов при его создании должно быть такое, чтобы скрипты ни в коем случае не стали узким местом производительности итоговой программы. на мой взгляд, это довольно важный момент. если все построено правильно, то скрипты не сильно понижают производительность. итак, надеюсь "зачем это нужно" вы уже прочитали и считаете, что использование скриптов в вашей программе - обдуманное решение, которое приведет к ускорению разработки и гибкости в использовании.
Итак, базовыми страницами, которые стоит посетить и почитать необходимую информацию, являются: официальная страница Lua, русскоязычный сайт Lua... документация по Lua: eng, рус. Также можно найти сжатую доходчивую информацию у Борескова А.В. здесь: общее, lua + с++. Лично мне понравилась лекция на gamedev'е: часть 1 и часть 2. Ну и многие зачастую ссылаются на пошаговый туториал здесь. всё, основные страницы, вроде бы, упомянул.
Про интеграцию Lua в Delphi первый раз я прочел на сайте glscene. Статья понравилась: после нее показалось, что все просто и прозрачно. но в последствие оказалось, что было бы замечательно к такой статье найти продолжение. что-то вроде "использование Lua в НЕ hello-world приложениях".
Итак, немного уточню своё замечание. первым делом неясно, как отрабатывают типы переменных. во-вторых ничего не сказано, про объявление самих переменных. в-третьих у меня не сработал luaL_DoString(), в-четвертых неясен принцип компиляции скриптов, ну и последнее, непонятно что там с уничтожением данных и возможными утечками памяти... первые два вопроса отпали после прочтения мануала. про типы данных в Lua настоятельно советую прочесть - многое станет ясно. про объявление переменных: могут быть локальные и глобальные. 
локальные объявляются просто: local a;
глобальные создаются при первом упоминании, вроде такого: a = 5;
насчет luaL_DoString() дело обстояло так... указанная в статье ссылка вела к хедерам, в которых в luaxlib.pas написано:

function luaL_dostring(L: Plua_State; const fn: PChar):Integer;
begin
    Result:=luaL_dofile(L, fn);
end;

переписав так:

function luaL_dostring(L: Plua_State; const fn: PChar):Integer;
var
    i: integer;
begin
    i := luaL_loadstring(L, fn);
    if i = 0 then
        i := lua_pcall(L, 0, LUA_MULTRET, 0);
    Result:=i;
end;
все заработало...

теперь о компиляции. сначала я использовал Lua так:
1. читаю из файла весь текст скрипта в PChar,
2. вызываю при необходимости luaL_dostring() для зачитанного скрипта
3. убиваю PChar-скрипт, когда он не нужен.

при этом я четко понимал, что скрипт перекомпилируется при каждом вызове luaL_dostring()... но как сначала один раз скомпилировать, а затем просить выполнить уже байт-код множество необходимых раз? оказывается, все просто. Lua довольно умный движок, который при вызове luaL_loadstring() компилирует поданный ему код и кладет скомпилированный байт-код на вершину стека. это вроде понятно и описано в документации. но вот то, чего я не нашел вот так сразу, так это то, что все функции и глобальные переменные, которые встречаются в переданном скрипте, компилируются отдельно и кладутся в глобальную таблицу Lua уже в скомпилированном виде при следующем вызове lua_pcall();.

таким образом, схема работы с Lua у меня поменялась на:
1. читаю из файла большим куском скрипт, который состоит на самом деле из набора функций,
2. вызываю для этого скриптового набора функций luaL_dostring(), который сначала скомпилирует, а затем разложит в глобальную таблицу все функции из скрипта,
3. при необходимости достаю из глобальной таблицы нужную функцию и выполняю уже скомпилированный байт-код.

осталось пояснить только третий пункт последней схемы. выполняется он в два этапа:
1. вытаскиваем байт-код искомой функции на вершину стека Lua_GetField(fLuaVM, Lua_GlobalsIndex, FunctionName);
2. и вызываем его на исполнение: Lua_PCall(fLuaVM, 0, Lua_Multret, 0);

стоит отметить, что такой вызов подходит для функций, не принимающих аргументы. для выполнения более сложных вызовов стоит ознакомиться со справкой. прочитав её, станет ясно, что, после вытаскивания функции из глобальной таблицы, необходимо затолкнуть в стек сами аргументы и передать их количество в Lua_PCall() вторым параметром.
также важным моментом является то, что результатом функции Lua_PCall() является код ошибки. если результат равен нулю, значит ошибки не произошло и можно двигаться дальше. если результат отличен от нуля, значит в ходе вызова байт-кода функции произошла ошибка, и ее расшифровка лежит на вершине стека.

казалось бы всё, но пока не совсем. последний вопрос, который у меня возник, это была неясность с освобождением байт-кода функции, который все время располагается в глобальной таблице Lua. в мануале сказано, что в таблице могут быть произвольные значения кроме nil, но нигде не было сказано (или я просто не нашел), что при попытке установить такое значение ячейке таблицы, предыдущее значение замечательно уничтожается, освобождая память, а ячейка, в которую мы пытались установить новое значение, равное nil, удаляется из таблицы.
таким образом освобождение глобальной таблицы от байт-кода загруженной нами функции, выполнится в два этапа:
1. Lua_PushNil(fLuaVM);
2. Lua_SetField(fLuaVM, Lua_GlobalsIndex, FunctionName);

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

Сообщения, схожие по тематике:

10 коммент.:

  1. без примера сложно что либо понять в этой статье =(

    ОтветитьУдалить
  2. Project, у меня нет своего хостинга. Я не знаю, куда можно залить, чтобы файл был с прямым линком и хранился максимально долго. Может что-то посоветуешь?

    ОтветитьУдалить
  3. залейте пожалуйста пару примеров использования луа из дельфи(хотябы на тот же narod от яндекса). бесплатный хостинг можно получить на ucoz [dot] com - создаёте сайт и получаете возможность хранить файлы на хостинге не ограниченно долго.
    меня интересует: выполнение скриптов луа посредством dofile и dostring, перенаправление ввода/вывода скрипта в программу на дельфи.
    спасибо!

    ОтветитьУдалить
  4. хочу прицепить возможность работы со скриптами путём создания плагина к своему приложения.
    проблема в следующем..
    lua ни в какую не хочет исполнять скрипты (точнее возможно он их и исполняет, однако мне об это никак не узнать - не могу вызвать из скрипта зарегистрированную мной в dll-ке плагина функцию).
    тот же самый код в exe-файле превосходно работает!
    возможно ли такое, что при регистрации функций lua берёт адрес функции, прописанной в dll, а при обращении из скрипта к данной функции библиотека lua51.dll ищет функцию в exe-файле, и как с этим бороться?
    стукните пожалуйста в асю: 478 259 099.. очень нужна консультация/помощь по lua - в инете нужной инфы не нахожу.
    спс

    ОтветитьУдалить
  5. Анонимный, привет!
    я выкладывал 4 демки по lua+delphi, о чем можно прочесть здесь:
    http://lampogolovii.blogspot.com/2010/09/blog-post.html
    там, в принципе, есть все основы для старта изучения этого скриптового движка!

    lenity, я никогда не использовал lua из dll'ки... хорошо, сегодня напишу тебе в аську))..

    ОтветитьУдалить
  6. вот это круче
    "Высокоуровневая библиотека CrystalLUA. Delphi + Lua":
    http://www.gamedev.ru/projects/forum/?id=140784

    ОтветитьУдалить
    Ответы
    1. Да-да! видел эту штуку! Но на мой взгляд CrystalLUA слишком монструозна... хотя, возможно, действительно ее стоит посмотреть поближе! благодарю за наводку!

      Удалить
    2. да фиг знает, вроде не монструозно
      вроде удобно даже

      кстати автор сейчас ищет единомышленников
      мож пригодишься чем-нибудь !

      Удалить
  7. Для хранения файлов можно использовать Dropbox. С прямым линком, 2GB места, можно докупить еще.

    ОтветитьУдалить
    Ответы
    1. уже несколько раз слышал про dropBox, но не разу не пользовался... видимо, время попробовать! обязательно зарегистрируюсь там в ближайшую неделю!

      Удалить