-
http://www.mediafire.com/?zsnwtgs4zxiреализация классовой обертки для Lua движка. Некоторые баги исправил. +фича. Которая позволяет из скрипта работать с внешним классом, как со своим родным. 1) дельфи класс должен быть наследником TLuaCompatibleClass и его поле FLuaClassName не должно быть пустым. 2) Сначала нужно добавить поддержк указанного класса методом AddClass 3) Далее нужно добавить указатели на метод-прослойку. Методо прослойка это обычный CFunction метод. Добавляется методом: AddClassMethod Obj - это указатель на класс, чей метод экспортируем. MethodName - имя метода. именно по этому имени из скрипта будет вызываться метод. MethodParams - список параметров метода. self указывать не надо. Method - адрес функции прослойки. Теперь при вызове какого-то метода через Call можно в качестве параметра указать класс и вызывать методы этого класса. Например: Скрипт: function Test(MyClass)
MyClass:Print(1,2);
end Дельфи код: TMyClass = class(TLuaCompatibleClass)
procedure Print(i,i2:integer);
end;
Функция прослойка:
Procedure TMyClass_Print(LuaState:Pointer):integer; cdecl;
var
Obj:TMyClass;
begin
Obj:=TMyClass(trunc(lua_tonumber(LuaState,-3)));
Obj.Print(trunc(lua_tonumber(LuaState,-2)),trunc(lua_tonumber(LuaState,-1)));
lua_pop(LuaState,3);
Result:=0;
end;
Добавление в скрипт:
Constructor MyClass.Create()
begin
FLuaClassName:='classMyClass';
LuaParser.AddClass(Self); LuaParser.AddClassMethod(Self, 'Print','Var1,Var2',@TMyClass_Print);
LuaParser.Call('Test',[Self],[]);
end;
-
Забыл сказать. у функций прослоек первый параметр в стэке всегда указатель на класс.
-
Спасибо, пригодится :).
Некоторые мысли:
- Почему бы вместо LuaClassName не использовать просто ClassName? Человек, пишуший скрипт все-равно это имя никак не использует, тогда не придется наследовать все классы от TLuaCompatibleClass (что может быть местами неудобно).
- Логично было бы в соответствующих местах (регистрация) передавать не сам объект, а его класс (TClass, либо class of TLuaCompartibleObject). Видимо, этому мешает то, что LuaClassName - свойство, но ClassName то вызывать можно и для класса.
- Нету поддержки published свойств, которую можно было бы реализовать.
- Вот еще идея, если договориться, что все используемые из луа методы будут иметь одинаковый список параметров (Parameters: array of const) и будут published, то, наверное, можно написать универсальную "прослойку", которая используя MethodAddress вызывает нужный метод. Тогда, не понадобиться возни с постоянной генерацией прослоек, да и регистрация методов будет не нужна, однако, функции, доступные из луа, будут принимать в качестве параметров массив вариантов, что может сделать содержащийся в них код менее понятным и более громоздким.
-
@!!ex, слушай, а ты можешь доступно вкратце рассказать что такое Lua и нафига оно нужно то? Частенько встречал в играх такое понятие, в программе StrongDC ;)
Я нифига понять не могу - что там такого? Чем он исполняется, нафига нужен?
-
> - Почему бы вместо LuaClassName не использовать просто ClassName? > Человек, пишуший скрипт все-равно это имя никак не использует, > тогда не придется наследовать все классы от TLuaCompatibleClass > (что может быть местами неудобно).
Низя. А если у нас описано два одинаковых дельфи класса в разных модулях, у них ClassName то одинаковый. При попытке добавить второй класс в луа получим башую ошибку... А так мы можем гарантировать > - Нету поддержки published свойств, которую можно было бы > реализовать.
Не понял... > - Вот еще идея, если договориться, что все используемые > из луа методы будут иметь одинаковый список параметров (Parameters: > array of const) и будут published, то, наверное, можно > написать универсальную "прослойку", которая используя MethodAddress > вызывает нужный метод. Тогда, не понадобиться возни с постоянной > генерацией прослоек, да и регистрация методов будет не нужна, > однако, функции, доступные из луа, будут принимать в качестве > параметров массив вариантов, что может сделать содержащийся > в них код менее понятным и более громоздким.
Не знаю. Смысла ИМХО нет. Функции прослойки - самый быстры, очевидный и языко-независимый метод. Лично для меня особенно последний пункт важен, поскольку приходится на разных языках писать. Портировать проще. > [3] Добежал (25.12.08 11:38)
Lua - язык скриптов. Код, который не зашит в exe. Позволяет часть функционала вынести из основного кода. Из преимуществ: Свободное(в разрешенных пределах) расширение функционала программы без изменения самого кода. Уегкое редактирование кода. В просто блокноте, не нужна IDE и компилятор. Платформо независимость. Практическое применение: В первую очередь игры. Когда мы создаем миссии, нужно задать поведение объектов на мире. Они должны что-то делать, реагировать на изменение ситуации. В принципе это все задается базовым поведением ИИ. Что было в старых стратегиях. Но если мы хотим сделать какое-то уникальное поведение и сюжет, то без скриптов не обойтись. Например когда игрок построит первый танковый завод, нужно создать амфибию с 5 пехотинцами внтури и высадить на базе игрока. как сделать? Player1.FirstWaveUsed = false;
function OnBuildingReady(Building)
if (Player1.FirstWaveUsed==false) and (Building:Owner==AI_PLAYER1) and (Building:Type==BUILDING_FACTORY) then
Amphibia = vAmphibia:new(iGuner:new(),iGuner:new(),iGuner:new(),iGuner:new(),iGuner:new(),n il,nil,nil,nil,nil);
Amphibia.Position = AmphibiaLandingZone.Position;
Amphibia:Attack(Nearest(Player1));
Player1.FirstWaveUsed = true;
end
end; Первая строчка - флаг, говорит о том, был ли активирован триггер. Функция, вызываемая при постройке нового здания. Проверяем, если триггер еще не активировался, если здание принадлежит игроку, если здание - завод. Создаем амфибию, пихаем в нее 5 солдат, еще 5 позицию остаются свободными. Устанавливаем позицию амфибии - AmphibiaLandingZone, это ранее заданная метка на карте. Задаем поведение Амфибии - атака ближайших объектов принадлежащих игроку Player1. Выставляем метку. Конечно, все тоже самое можно зашить в код игры. Но это глупо по трем причина: 1) левелдизайнеры вынуждены трясти программистов движка для добавления ИИ. что не есть шибко хорошо. 2) зашивать конкретные миссии в движок, это примерно тоже самое, как зашить в GPS машины только 10 конкретных путей. 3) Доступные из вне скрипты - это легкий путь для создания сторонних модификаций. half-Life 1 до сих пор жив, за счет продуманной системы создания новых модов.
-
>Практическое применение: > В первую очередь игры.
Слишком сильное заявление :)
-
> [5] Jeer © (25.12.08 12:51)
Ну я в первую очередь играми занимаюсь, атк что в первую очередь игры, остальное вне моей компетенции.
-
*Ну я в первую очередь играми занимаюсь, атк что в первую очередь игры, остальное вне моей компетенции. :)
-
> Низя. А если у нас описано два одинаковых дельфи класса > в разных модулях, у них ClassName то одинаковый. При попытке > добавить второй класс в луа получим башую ошибку... А так > мы можем гарантировать
Интересно, как мы можем гарантировать, что у них LuaClassName не одинаковый :). И ClassName и LuaClassName задает программист так или иначе, почему он каким-то волшебным образом может гарантировать второе, но не может первое. В общем, проблема, ИМХО, надумана).
> > - Нету поддержки published свойств, которую можно было > бы > > реализовать. > > Не понял...
У делфевых классов есть такая штука как property, о чем вы несомненно знаете. published отличаются от остальных тем, что с ними можно работать в рантайм по имени (писать, читать, получать тип, на чем основано сохранение/загрузка *.dfm). Соответственно, можно без особых затруднений сделать, чтобы конструкции obj.prop = "prop value" и local temp = obj.prop в луа приводили к записи и чтению соответствующего свойства, если таковое имеется (через перекрывание того самого __index и __newindex).
> Функции прослойки - самый быстры, очевидный и языко-независимый > метод. > Лично для меня особенно последний пункт важен, поскольку > приходится на разных языках писать. Портировать проще.
Для меня это не так актуально, так что я попробую по-своему :). Если портируемость во главе угла, возможно имело смысл все-таки писать изначально на си, компиляторы которого есть практически под любую кофеварку, чем писать на делфи, а потом переводить?
-
> Интересно, как мы можем гарантировать, что у них LuaClassName > не одинаковый :). И ClassName и LuaClassName задает программист > так или иначе, почему он каким-то волшебным образом может > гарантировать второе, но не может первое. В общем, проблема, > ИМХО, надумана).
Classname программист не задает. Он задает имя класс. А вот FLUaClassName задает. и может следить за тем, чтобы это имя было уникальным.
> У делфевых классов есть такая штука как property, о чем > вы несомненно знаете. published отличаются от остальных > тем, что с ними можно работать в рантайм по имени (писать, > читать, получать тип, на чем основано сохранение/загрузка > *.dfm). Соответственно, можно без особых затруднений сделать, > чтобы конструкции obj.prop = "prop value" и local temp > = obj.prop в луа приводили к записи и чтению соответствующего > свойства, если таковое имеется (через перекрывание того > самого __index и __newindex).
Я так и думал. Не представляю пока, как это можно реализовать на Lua.
> Для меня это не так актуально, так что я попробую по-своему > :). Если портируемость во главе угла, возможно имело смысл > все-таки писать изначально на си, компиляторы которого есть > практически под любую кофеварку, чем писать на делфи, а > потом переводить?
Конкретно этот проект - дельфи. И он врядли будет портирован. Однако эот не помешает портировать конкретно реализацию класса для работы с ЛУа для длургого проекта. :)
-
>@!!ex © (25.12.08 12:48) [4]
ясно, спасибо.
То есть, по сути это некий универсальный скриптовый движок. Интепретатор которого сделан под большое количество ОС?
А под винды он как идет, как DLL'ка?
-
> То есть, по сути это некий универсальный скриптовый движок. > Интепретатор которого сделан под большое количество ОС?
Да. Причем очень быстрый. Он не просто интрепритирет команды, но умеет их компилировать в байткод.
> А под винды он как идет, как DLL'ка?
Да.
-
> Classname программист не задает. Он задает имя класс.
ClassName (англ.) - имя класса, так что ваше утверждение для меня звучит как абсурдное ^_^. То, что он его задает не в виде ClassName := 'MyName', а в виде TMyName = class() для меня принципиально ничего не меняет, следить за уникальностью имени класса он все-равно может. Впрочем, переубеждать не стану, наверное вам, в рамках вашей конкретной игры, виднее (тем более с размахом на портирование, где ClassName не будет).
> Я так и думал. Не представляю пока, как это можно реализовать > на Lua.
А я пробовал, реализация больших сложностей не составляет. Даже линк с работающим примером оставлял в вашей самой первой ветке о луа, созданной несколько дней назад. Вот только все еще не собрался с вызовом методов разобраться, но доберусь и до этого (все-таки хочу попробовать реализовать вариант без ручных прослоек).
-
> ClassName (англ.) - имя класса, так что ваше утверждение > для меня звучит как абсурдное ^_^. То, что он его задает > не в виде ClassName := 'MyName', а в виде TMyName = class() > для меня принципиально ничего не меняет, следить за уникальностью > имени класса он все-равно может. Впрочем, переубеждать не > стану, наверное вам, в рамках вашей конкретной игры, виднее > (тем более с размахом на портирование, где ClassName не > будет).
Блин. Я русским языком написал, в чем проблема. Странный спор, ей богу.
> А я пробовал, реализация больших сложностей не составляет. > Даже линк с работающим примером оставлял в вашей самой первой > ветке о луа, созданной несколько дней назад. Вот только > все еще не собрался с вызовом методов разобраться, но доберусь > и до этого (все-таки хочу попробовать реализовать вариант > без ручных прослоек).
Попробуйте реализовать.
-
Фактически, если мы зацикливаемся только на дельфи, то нет никаких проблем в том, что thiscall реализовать самому. Тоесть при регистрации метода задается адрес некого универсального метода, который в качестве первого параметра получает смещение для VMT. Тогда зная VMT он кладет параметры в стэк(не Lua стэк, а нормальный) и делает call. реализуется не очень сложно, но мне не нравится из-за привязанности к дельфи(и даже больше - привязки к компилятору)
-
> Фактически, если мы зацикливаемся только на дельфи, то нет > никаких проблем в том, что thiscall реализовать самому. > Тоесть при регистрации метода задается адрес некого универсального > метода, который в качестве первого параметра получает смещение > для VMT. Тогда зная VMT он кладет параметры в стэк(не Lua > стэк, а нормальный) и делает call. > реализуется не очень сложно, но мне не нравится из-за привязанности > к дельфи(и даже больше - привязки к компилятору)
Реализуется сложно, ибо если из Lua передали число 5, то черте его знает, то ли это Byte, то ли Integer, то ли Int64, то ли Double. Соответственно, нужна функция регистрации методов, которой надо указывать и перечислять все их параметры и их типы (включая размер, собственно, вся проблема в том, что это не храниться в RTTI, о чем и было обсуждение в разделе "Игры"). А именно этой громоздкой регистрации хотелось избежать.
Идея была в том, чтобы привести все методы, доступные из луа, к одному типу и сделать их published.
TLuaMethod = procedure (Parameters: array of const) of object;
И вызывать их по имени, получая адрес с помощью MethodAddress и приводя полученный Pointer и экземпляр, над которым выполняется метод, в переменную типа TLuaMethod. Надо только убедить луа вызывать нашу функцию, когда ей нужно вызвать метод.
Пусть __index - обработчик на делфи, вызываемый при обращении к полю объекта. То есть, обычная cdecl функция, принимающая LuaState, которая зарегистрирована в Lua. Тогда, внутри нее мы должны: 1) запомнить в своем стеке (созданном внутри TLuaParser, например) адрес функции, полученный с помощью MethodAddress, и Self - для какого объекта вызывать этот метод. Self мы получаем из стека луа (либо это user_data, либо это определенное поле таблицы (classPointer), если объект внутри луа представлен как таблица). 2) Вернуть в качестве результата зарегистрированную универсальную функцию делфи (назовем ее DelphiMethodCall), единственную для любого метода и объекта. Эта функция, также как и __index принимает параметр LuaState, имеет тип cdecl и т.д.
DelphiMethodCall производит следующие действия: 1) достает из луа стека все параметры, и складывает их в массив вариантов. 2) достает из нашего стека адрес метода и указатель на экземпляр класса, и вызывает соответствующий метод, приведя его к типу TLuaMethod и передав полученные на первом шаге параметры.
Вот, в общем, как оно задумывалось. Стек нужен, так как возможны вложенные вызовы методов. Осталось реализовать, надеюсь сделаю в выходные.
Особенности метода:
[+] не нужна ручная регистрация методов, все методы описанные в published автоматически доступны из lua. [+] не нужны процедуры переходники. [-] все процедуры, доступные луа должны иметь один параметр типа array of const (и возможно, одно возвращаемое значение типа Variant). [-] привязан к языку, не представляю как это портировать на си.
-
вы не правы. Нам не нужно знать какой тип, byte, integer или double. есть три типа: number, string, boolean. Если мы хотим вызывать некий метод из Lua, то должно соблюдаться лишь одно условие: этот метод в качестве параметров должен принимать только переменные трех типов: double, string, boolean. и нет проблем.
-
> вы не правы. > Нам не нужно знать какой тип, byte, integer или double. > есть три типа: number, string, boolean. > Если мы хотим вызывать некий метод из Lua, то должно соблюдаться > лишь одно условие: > этот метод в качестве параметров должен принимать только > переменные трех типов: double, string, boolean. > и нет проблем.
Есть проблемы :). Если ввести такие дополнительные ограничения на типы получаемых параметров - то да, проблем становиться меньше, но не все они исчезают. Это решение все-равно требует регистрации методов (что меня не радует само по себе, если есть шанс уговорить компилятор это сделать за меня). Тем более, оно все равно требует передавать для регистрации (и будущего хранения) кроме количества параметров еще и их типы (double, string, boolean), для правильного сигнализирования об ошибке. Если есть вызов из луа
myobject.DoSomething(123);
при том, в делфи, мы ожидаем вместо числа, скажем, строку. Такой вызов, скорее всего закончиться плачевно (мы передадим Integer (ну или теперь Double) как параметр, но метод будет ожидать строку, и обращение к строке выльется в AV). То есть, нам надо еще и регистрировать параметры каждого метода (их тип), кроме регистрации самого метода, и где-то хранить их.
Массив вариантов лишен этого недостатка, если скриптописатель ненароком передаст вместо одного типа другой, вопросы конвертации возьмет на себя RTL. Ну и возможность избежать регистрации методов все-таки манит :).
-
> [17] Servy © (25.12.08 23:57)
Не пойму что хорошего в том, что из Lua будет доступен ВЕСЬ код. Скрипту просто не нужно знать все. Даже больше - это вредно. Скрипт - это инструмент более выского уровня, работающий не с конкретными данными а с их более высокими представлениями. За счет чего программирование скрипта проще. Мы не задаем движение объета каждый кадр. Мы просто указываем куда двигаться. А досутп ко всему коду, снизит уровень до уровня дельфи. Что усложнит сам принцип работы. Что уж мелочиться, нафиг нам вообще Lua, если можно просто прикрутить dll и в ней спокойно писать нужный код...
-
> Не пойму что хорошего в том, что из Lua будет доступен ВЕСЬ > код. > Скрипту просто не нужно знать все. Даже больше - это вредно.
Скрипту не будет и не может быть доступен весь код. Ему будут доступны исключительно методы и свойства, описанные как published, причем не всех классов, а только тех, которые передаются в луа код как параметры функций или возвращаемые значения. Все, что вы не хотите делать доступным скрипту можно объявить как public/protected/private. Просто, такой способ дифференциации методов на доступные/недоступные из скрипта мне казался значительно прозе и нагляднее :). Может я и не прав.
|