Скриптовые языки в рогаликах - как это делается? (Lua)

Темы, связанные с проектированием и программированием roguelike-игр

Модераторы: Sanja, Максим Кич

altmax
Сообщения: 173
Зарегистрирован: 15 сен 2012, 11:59

Re: Скриптовые языки в рогаликах - как это делается? (Lua)

Сообщение altmax » 25 апр 2018, 09:05

Итак, начал писать свой туториал по использованию lua в рогаликах. Ну как свой — на самом деле его написал eliasdaler, основная его статья находится здесь — https://habrahabr.ru/post/237503/. Вместе с тем, в этой статье раскрыты далеко не все нюансы, точнее говоря, он подробно описывает, как из Lua запустить функцию С++, и наоборот, как из С++ запустить скрипт Lua. Но этого мало для корректного использования скриптов Lua в нашем рогалике, нам нужна полноценная интеграция, чтобы из скриптов мы могли обращаться к классам в С++ и корректно использовать их методы.
Решение данного вопроса я тоже нашел в трудах вышеупомянутого eliasdaler, но конретно про данный нюанс он нигде не писал. Чтобы найти это решение, потребовалось поковыряться в исходниках игры, которую он пишет. Анализ фрагментов кода помог, и я за 15 минут решил проблему, которую до этого почти месяц безуспешно пытался решить. Самое главное, что про биндинг методов из С++ в скрипты Lua нигде подробно не написано. Везде ограничиваются общими словами. Ну да, теперь то мне тоже понятно, как это сделать, но тогда — месяц я бился над данным вопросом. Ну конечно не 7 дней в неделю по 12 часов, параллельно я занимался и другими делами, но данный вопрос периодически возникал и я его никак не мог решить, что меня безмерно расстраивало.
Итак, в начале практически полная перепечатка (а точнее копипаст) его статьи с Хабра, чтобы разобраться с основами. Там всё очень хорошо и подробно описано:

Данная статья — перевод моего туториала, который я изначально писал на английском. Однако этот перевод содержит дополнения и улучшения по сравнению с оригиналом.
Туториал не требует знания Lua, а вот C++ нужно знать на уровне чуть выше базового, но сложного кода здесь нет. 

Когда-то я написал статью про использование Lua с C++ с помощью Lua C API. В то время, как написать простой враппер для Lua, поддерживающий простые переменные и функции, не составляет особого труда, написать враппер, который будет поддерживать более сложные вещи (функции, классы, исключения, пространства имён), уже затруднительно. 
Врапперов для использования Lua и C++ написано довольно много. С многими из них можно ознакомиться здесь.
Я протестировал многие из них, и больше всего мне понравился LuaBridge. В LuaBridge есть многое: удобный интерфейс, exceptions, namespaces и ещё много всего. 
Но начнём по порядку, зачем вообще использовать Lua c С++?

Зачем использовать Lua?

Конфигурационные файлы. Избавление от констант, магических чисел и некоторых define'ов


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

Код: Выделить всё

window = {
    title = "Test project",
    width = 800,
    height = 600
}

Можно получать системные переменные:

Код: Выделить всё

homeDir = os.getenv("HOME")

Можно использовать математические выражения для задания параметров:

Код: Выделить всё

someVariable = 2 * math.pi
Скрипты, плагины, расширение функциональности программы


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

Немного о Lua. Lua — язык с лицензией MIT, которая позволяет использовать его как в некоммерческих, так и в коммерческих приложениях. Lua написан на C, поэтому Lua работает на большинстве ОС, что позволяет использовать Lua в кросс-платформенных приложениях без проблем.
Установка Lua и LuaBridge


Итак, приступим. Для начала скачайте Lua и LuaBridge
Добавьте include папку Lua и сам LuaBridge в Include Directories вашего проекта
Также добавьте lua52.lib в список библиотек для линковки.

Создайте файл script.lua со следующим содержанием:
-- script.lua

Код: Выделить всё

testString = "LuaBridge works!"
number = 42

Добавьте main.cpp (этот код лишь для проверки того, что всё работает, объяснение будет чуть ниже):

Код: Выделить всё

// main.cpp
#include <LuaBridge.h>
#include <iostream>
extern "C" {
# include "lua.h"
# include "lauxlib.h"
# include "lualib.h"
}
 
using namespace luabridge;
int main() {
    lua_State* L = luaL_newstate();
    luaL_dofile(L, "script.lua");
    luaL_openlibs(L);
    lua_pcall(L, 0, 0, 0);
    LuaRef s = getGlobal(L, "testString");
    LuaRef n = getGlobal(L, "number");
    std::string luaString = s.cast<std::string>();
    int answer = n.cast<int>();
    std::cout << luaString << std::endl;
    std::cout << "And here's our number:" << answer << std::endl;
}

Скомпилируйте и запустите программу. Вы должны увидеть следующее:

Код: Выделить всё

LuaBridge works!
And here's our number:42
Примечание: если программа не компилируется и компилятор жалуется на ошибку “error C2065: ‘lua_State’: undeclared identifier” в файле LuaHelpers.h, то вам нужно сделать следующее:
1) Добавьте эти строки в начало файла LuaHelpers.h

Код: Выделить всё

extern "C" {
# include "lua.h"
# include "lauxlib.h"
# include "lualib.h"
}

2) Измените 460ую строку Stack.h с этого:

Код: Выделить всё

lua_pushstring (L, str.c_str(), str.size());
На это:

Код: Выделить всё

lua_pushlstring (L, str.c_str(), str.size());

Готово!

А теперь подробнее о том, как работает код. 

Включаем все необходимые хэдеры:

Код: Выделить всё

#include <LuaBridge.h>
#include <iostream>
extern "C" {
# include "lua.h"
# include "lauxlib.h"
# include "lualib.h"
}

Все функции и классы LuaBridge помещены в namespace luabridge, и чтобы не писать «luabridge» множество раз, я использую эту конструкцию (хотя её лучше помещать в те места, где используется сам LuaBridge)
using namespace luabridge;


Создаём lua_State

Код: Выделить всё

lua_State* L = luaL_newstate();
Открываем наш скрипт. Для каждого скрипта не нужно создавать новый lua_State, можно использовать один lua_State для множества скриптов. При этом нужно учитывать коллизию переменных в глобальном нэймспейсе. Если в script1.lua и script2.lua будут объявлены переменные с одинаковыми именами, то могут возникнуть проблемы

Код: Выделить всё

luaL_dofile(L, "script.lua");

Открываем основные библиотеки Lua(io, math, etc.) и вызываем основную часть скрипта (т.е. если в скрипте были прописаны действия в глобальном нэймспейсе, то они будут выполнены)

Код: Выделить всё

luaL_openlibs(L);
lua_pcall(L, 0, 0, 0);

Создаём объект LuaRef, который может хранить себе всё, что может хранить переменная Lua: int, float, bool, string, table и т.д.

Код: Выделить всё

LuaRef s = getGlobal(L, "testString");
LuaRef n = getGlobal(L, "number");

Преобразовать LuaRef в типы C++ легко:

Код: Выделить всё

std::string luaString = s.cast<std::string>();
int answer = n.cast<int>();
Проверка и исправление ошибок


Но некоторые вещи могут пойти не так, и стоит производить проверку и обработку ошибок. Рассмотрим наиболее важные и часто встречающиеся ошибки
Что, если скрипт Lua не найден?

Код: Выделить всё

if (luaL_loadfile(L, filename.c_str()) ||
    lua_pcall(L, 0, 0, 0)) {
    ... // скрипт не найден
}
Что, если переменная не найдена?


Переменная может быть не объявлена, либо её значение — nil. Это легко проверить с помощью функции isNil()

Код: Выделить всё

if (s.isNil()) {
    std::cout << "Variable not found!" << std::endl;
}

Переменная не того типа, который мы ожидаем получить

Например, ожидается, что переменная имет тип string, тогда можно сделать такую проверку перед тем как делать каст:

Код: Выделить всё

if(s.isString()) {
    luaString = s.cast<std::string>();
}
Таблицы


Таблицы — это не просто массивы: таблицы — замечательная структура данных, которая позволяет хранить в них переменные Lua любого типа, другие таблицы и ставить ключи разных типов в соответствие значениям и переменным. Таблицы позволяют представлять и получать конфигурационные файлы в красивом и легкочитаемом виде. 

Создайте script.lua с таким содержанием:

Код: Выделить всё

window = {
    title = "Window v.0.1",
    width = 400,
    height = 500
}

Код на C++, позволяющий получить данные из этого скрипта:

Код: Выделить всё

LuaRef t = getGlobal(L, "window");
LuaRef title = t["title"];
LuaRef w = t["width"];
LuaRef h = t["height"];
std::string titleString = title.cast<std::string>();
int width = w.cast<int>();
int height = h.cast<int>();
std::cout << titleString << std::endl;
std::cout << "width = " << width << std::endl;
std::cout << "height = " << height << std::endl;

Вы должны увидеть на экране следующее:

Код: Выделить всё

Window v.0.1
width = 400
height = 500
Как видите, можно получать различные элементы таблицы, используя оператор []. Можно писать короче:

Код: Выделить всё

int width = t["width"].cast<int>();

Можно также изменять содержимое таблицы:

Код: Выделить всё

t["width"] = 300

Это не меняет значение в скрипте, а лишь значение, которое содержится в ходе выполнения программы. Т.е. происходит следующее:

Код: Выделить всё

int width = t["width"].cast<int>(); // 400
t["width"] = 300
width = t["width"].cast<int>(); // 300

Чтобы сохранить значение, нужно воспользоваться сериализацией таблиц(table serialization), но данный туториал не об этом. 

Пусть теперь таблица выглядит так:

Код: Выделить всё

window = {
    title = "Window v.0.1",
    size = {
        w = 400,
        h = 500
    }
}

Как можно получить значение window.size.w?
Вот так:

Код: Выделить всё

LuaRef t = getGlobal(L, "window");
LuaRef size = t["size"];
LuaRef w = size["w"];
int width = w.cast<int>();
Функции


Давайте напишем простую функции на C++

Код: Выделить всё

void printMessage(const std::string& s) {
    std::cout << s << std::endl;
}

И напишем вот это в скрипте на Lua:

Код: Выделить всё

printMessage("You can call C++ functions from Lua!")

Затем мы регистрируем функцию в C++

Код: Выделить всё

getGlobalNamespace(L).
   addFunction("printMessage", printMessage);

Примечание 1: это нужно делать до вызова «luaL_dofile», иначе Lua попытается вызвать необъявленную функцию
Примечание 2: Функции на C++ и Lua могут иметь разные имена

Данный код зарегистрировал функцию в глобальном namespace Lua. Чтобы зарегистрировать его, например, в namespace «game», нужно написать следующий код:

Код: Выделить всё

getGlobalNamespace(L).
    beginNamespace("game")
        .addFunction("printMessage", printMessage)
    .endNamespace();

Тогда функцию printMessage в скриптах нужно будет вызывать данным образом:

Код: Выделить всё

game.printMessage("You can call C++ functions from Lua!")

Пространства имён в Lua не имеют ничего общего с пространствами имён C++. Они скорее используются для логического объединения и удобства.

Теперь вызовем функцию Lua из C++
-- script.lua

Код: Выделить всё

sumNumbers = function(a,b)
    printMessage("You can still call C++ functions from Lua functions!")
    return a + b
end
// main.cpp

Код: Выделить всё

LuaRef sumNumbers = getGlobal(L, "sumNumbers");
int result = sumNumbers(5, 4);
std::cout << "Result:" << result << std::endl;

Вы должны увидеть следующее:

Код: Выделить всё

You can still call C++ functions from Lua functions!
Result:9
Разве не замечательно? Не нужно указывать LuaBridge сколько и каких аргументов у функции, и какие значения она возвращает. 
Но есть одно ограничение: у одной функции Lua не может быть более 8 аргументов. Но это ограничение легко обойти, передав таблицу, как аргумент.

Если вы передаёте в функцию больше аргументов, чем требуется, LuaBridge молча проигнорирует их. Однако, если что-то пойдёт не так, то LuaBridge сгенерирует исключение LuaException. Не забудьте словить его! Поэтому рекомендуется окружать код блоками try/catch

Вот полный код примера с функциями:
-- script.lua

Код: Выделить всё

printMessage("You can call C++ functions from Lua!")
 
sumNumbers = function(a,b)
    printMessage("You can still call C++ functions from Lua functions!")
    return a + b
end
// main.cpp

Код: Выделить всё

#include <LuaBridge.h>
#include <iostream>
extern "C" {
# include "lua.h"
# include "lauxlib.h"
# include "lualib.h"
}
 
using namespace luabridge;
 
void printMessage(const std::string& s) {
    std::cout << s << std::endl;
}
 
int main() {
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);
    getGlobalNamespace(L).addFunction("printMessage", printMessage);
    luaL_dofile(L, "script.lua");
    lua_pcall(L, 0, 0, 0);
    LuaRef sumNumbers = getGlobal(L, "sumNumbers");
    int result = sumNumbers(5, 4);
    std::cout << "Result:" << result << std::endl;
    system("pause");
}
Что? Есть ещё что-то?


Да. Есть ещё несколько замечательных вещей, о которых я напишу в последующих частях туториала: классы, создание объектов, срок жизни объектов… Много всего!
Также рекомендую прочитать этот dev log, в котором я рассказал о том, как использую скрипты в своей игре, практические примеры всегда полезны.

Теперь снова моё:
Кстати, обещание он свое не выполнил, и про упомянутые несколько замечательных вещей так и не написал. Хотя обещанного три года ждут, а когда я искал, прошло всего два с половиной года, может потом он это и дописал. Но было уже поздно, решение было найдено и без его записей.
Кстати, есть такая замечательная вещь, как паттерны программирования. И там есть такой паттерн, как «прокси» - структурный шаблон проектирования, предоставляющий объект, который контролирует доступ к другому объекту, перехватывая все вызовы (выполняет функцию контейнера). Написано страшно и непонятно, но именно его мы и будем использовать при обращении из скриптов к классам в С++.
Будет создан отдельный класс, который и будет биндить(переносить) все нужные нам методы в скрипт Lua, причем все эти методы будут содержаться в этом же самом классе. Т.е по сути этот класс будет дублировать 100, 200 или более необходимых нам методов других классов. С одной стороны, это излишне — лишний расход памяти, ресурсов процессора и т. п. С другой стороны, если у нас изменится какой-то метод в основном классе, скажем, станет принимать не 3, а 2 параметра, то у нас не будет необходимости перелопачивать десятки килобайт наших скриптов в поисках обращения к этому методу, чтобы там поправить должным образом наш скрипт. Нам будет достаточно в нашем прокси-классе найти этот метод и поправить обращение к методу основного класса только там. Скрипты Lua будут по прежнему передавать три параметра, но в нашу программу на С++ пойдут только два из них.
Итак, для начала создаем наш прокси-класс. Я его назвал LuaAdapter — и это было основной большой ошибкой, т. к. название класса нам придется писать по многих местах и не по одному разу, и, согласитесь, что написать La, например, гораздо проще, чем LuaAdapter. Есть конечно различные помощники с автозаполнением в средствах разработки, но всё равно, чем короче будет название класса, тем проще будет в дальнейшем.
В LuaAdapter.h в разделе public, к примеру, прописываем следующие методы (в дальнейшем буду приводить куски моего рабочего кода):

Код: Выделить всё

int GetGamerX () const;
	void SetGamerX ( int a);
	int GetGamerY () const;
	void SetGamerY (int a);
	int GetGamerHP () const;
	void SetGamerHP (int a);
Соответственно эти методы получают координаты X,Y, здоровье игрока и задают их. Если посмотреть определение этих методов, то там увидим следующее:



Код: Выделить всё

int LuaAdapter::GetGamerX () const 
{
	return MyGamer->GetCoordX ();
}

int LuaAdapter::GetGamerY () const 
{
	return MyGamer->GetCoordY ();
}

void LuaAdapter::SetGamerX (int a)
{
	MyGamer->SetCoordX (a);
	return;
}

void LuaAdapter::SetGamerY (int a)
{
	MyGamer->SetCoordY (a);
	return;
}
Т.е. в данном случае мы просто вызываем гетеры и сетеры класса Gamer через указатель на него MyGamer. Указатель этот объявляется в приватной части класса и инициализируется в конструкторе. Тут возможны некоторые варианты — если этот самый указатель инициализировать не в конструкторе, а непосредственно в методе, то это приведет к некоторой избыточности кода и лишнему расходу памяти, но, с другой стороны, позволит создавать экземпляр класса LuaAdapter раньше создания класса Gamer. Правда обращаться к методам не получится — выдаст ошибку времени выполнения, т. к. класса Gamer еще не существует и вызвать его методы не получится.

Ну и теперь самое интересное — непосредственно биндинг нужных нам функций в скрипты Lua. Там опять есть два варианта — такие простые функции с одним аргументом и без него мы можем передать собственно как отдельные функции, а можем как свойства — properties. Во втором случае ими будет удобнее пользоваться в скриптах Lua, но если функции принимают несколько параметров, то передать их как свойства уже не получится. Я приведу оба способа для понимания.

Первый — как свойства. Создаем метод с указанным синтаксисом и прописываем всё как на примере. Метод этот тоже надо не забыть объявить в заголовочном файле.

Код: Выделить всё

void LuaAdapter::LuaDesc (lua_State *L)
{
	
	
	getGlobalNamespace(L)
			.beginClass <LuaAdapter> ("Game")
			.addProperty ("GamerX", &LuaAdapter::GetGamerX, &LuaAdapter::SetGamerX)
			.addProperty ("GamerY", &LuaAdapter::GetGamerY, &LuaAdapter::SetGamerY)
			.addProperty ("GamerHP", &LuaAdapter::GetGamerHP, &LuaAdapter::SetGamerHP)
		.endClass();
}
В данном случае мы передаем в скрипт Lua свойства GamerX, GamerY, GamerHP и обращаемся к ним как к обычным переменным:

Код: Выделить всё

A = Game:GamerX
Game:GamerY = 56
Game:GamerHP = 20
Названия можно давать какие угодно, главное в скриптах обращаться точно так же. Иначе из-за ошибки в одной букве будете получать исключения во время выполнения и долго-долго искать, где же ошиблись.

Второй способ — передаем как функции, ничего не изменяя, соответственно и обращаться с ними будем в дальнейшем как с функциями:

Код: Выделить всё

void LuaAdapter::LuaDesc (lua_State *L)
{
	
	
	getGlobalNamespace(L)
			.beginClass <LuaAdapter> ("Game")
			.addFunction ("GetGamerX", &LuaAdapter::GetGamerX)
			.addFunction ("SetGamerX", &LuaAdapter::SetGamerX)
			.addFunction ("GetGamerY", &LuaAdapter::GetGamerY)
			.addFunction ("SetGamerY", &LuaAdapter::SetGamerY)
			.addFunction ("GetGamerHP", &LuaAdapter::GetGamerHP)
			.addFunction ("SetGamerHP", &LuaAdapter::SetGamerHP)			
		.endClass();
}
Ну и пример обращения к функциям в скриптах:

Код: Выделить всё

A = Game:GetGamerX()
Game:SetGamerY (56)
Game:SetGamerHP (20)
И да, этот метод получается очень большим, просто нереально большим по размеру, т. к. каждая экспортируемая функция — это отдельная строка. У меня, например, в этом методе 170 строк, и это далеко не предел.
Итак, класс-адаптер мы создали, теперь же осталось решить вопрос с передачей из программы на С++ управления нашему скрипту на Lua. Для этого в методе, где надо передать управление, пишется нижеследующая конструкция. По выходу из скрипта Lua управление опять передается следующему оператору в программе.

Код: Выделить всё

using namespace luabridge;

	lua_State* L = luaL_newstate();
   	luaL_openlibs(L);
	LuaAdapter Luaad;
	Luaad.LuaDesc(L);
	luaL_dofile(L, ".\\Files\\lua\\MyScript.lua");
   	lua_pcall(L, 0, 0, 0);
   	LuaRef MyScript = getGlobal(L, "MyScript");
	MyScript (Luaad);
Т.е. мы создаем наш интерпретатор lua, подключаем туда все библиотеки, создаем экземпляр класса LuaAdapter, запускаем в экзмепляре нашего класса-адаптера собственно сам биндинг функций в скрипты, потом показываем какой именно файл скрипта надо открыть, затем указываем, какую именно функцию надо выполнить в этом скрипте. Ну и на последок уже вызываем эту самую функцию из Lua и передаем туда в качестве параметра наш класс-адаптер. В скрипте Lua соответственно всё будет выглядеть следующим образом:

Код: Выделить всё

MyScript = function (Game)
Game:SetGamerY(56)
NewCoord = Game:GetCoordX()
…...
return




Т.е. В данном скрипте мы в качестве одного из параметров получили наш класс-прокси и успешно обратились к нему, вызвав необходимые функции. Что в общем то и требовалось получить.
И еще одно дополнение. Этот самый luabridge уже не поддерживается и вообще несколько кривоват, при каждом вызове lua_State* L = luaL_newstate(); идет утечка памяти примерно в районе 10 килобайт. Соответственно, если при каждом обращении к скриптам создавать эту самую виртуальную машину Lua заново, то каждый раз из памяти будет тратиться около 10 кб памяти. А у меня, например, на каждый ход идет как минимум одно обращение, анализирующее ход игрока, а так же по обращению от каждого моба на уровне — т. е. каждый ход утекало около 200 кб памяти. Потому удобнее будет создание этой переменной, так же как и создание экземпляра нашего прокси-класса, сделать один раз в начале выполнения нашей программы, а потом просто получать указатели на них в нужный момент времени.

Аватара пользователя
karagy
Сообщения: 1271
Зарегистрирован: 10 янв 2007, 14:13

Re: Скриптовые языки в рогаликах - как это делается? (Lua)

Сообщение karagy » 25 апр 2018, 11:54

Я, конечно, извиняюсь, но, это следовало выложить на habr.com.
А тут просто оставить ссылку.

altmax
Сообщения: 173
Зарегистрирован: 15 сен 2012, 11:59

Re: Скриптовые языки в рогаликах - как это делается? (Lua)

Сообщение altmax » 25 апр 2018, 13:44

karagy писал(а):
25 апр 2018, 11:54
Я, конечно, извиняюсь, но, это следовало выложить на habr.com.
А тут просто оставить ссылку.
Там первые 60% простая копипаста с этого же хабра, и только 40% примерно мои. Лучше в вики у нас добавить.

Аватара пользователя
karagy
Сообщения: 1271
Зарегистрирован: 10 янв 2007, 14:13

Re: Скриптовые языки в рогаликах - как это делается? (Lua)

Сообщение karagy » 16 май 2018, 18:31

Недавно попробовал Very Simple Lua - бридж для Delphi (pascal).
Очень понравилось.

Аватара пользователя
Apromix
Мастер
Сообщения: 1236
Зарегистрирован: 04 июл 2011, 10:44
Откуда: Украина, Черновцы
Контактная информация:

Re: Скриптовые языки в рогаликах - как это делается? (Lua)

Сообщение Apromix » 16 май 2018, 21:09

Для более старых версий дельфи был (и есть :) ) CrystalLua.

Аватара пользователя
karagy
Сообщения: 1271
Зарегистрирован: 10 янв 2007, 14:13

Re: Скриптовые языки в рогаликах - как это делается? (Lua)

Сообщение karagy » 17 май 2018, 10:25

Мне понадобились скриптовые формулы в дельфийской программе. Причем быстрые.
подробности выбора в спойлере
Скрытый текст: ПОКАЗАТЬ
Я глянул разные DSL-и, но они оказались медленными.
Тестировалось 1000000 (миллион) вызовов скриптовой функции из дельфей.
PascalScript - примерно 4 секунды.
Очень хорош PegtopFormulas (примерно 20 миллисекунд), но он - чистая формула, без управления потоком выполнения (никаких условий и циклов).
Скрипты из JCL/JVCL - тоже не быстрые.
Оглядывая странную скудность DSL-й в инете, я конечно понимал, что для серъезных вещей нанимают мальчиков, умеющих делать DSL промышленного качества через lex/yacc. Но ощущение, что я что-то упускаю - оставалось.
И тут я вспомнил про lua.

Глянул CrystalLUA. О нем, в своё время, хорошо отзывался Александр Багель.
Но у меня XE7 - и переписывать CrystalLUA с char/string на AnsiiChar/AnsiiString (и еще кучу устаревших мелочей) - мне не захотелось.

Глянул verysimplelua. Автор хорош в дельфях, но не гуру. В его блоге, мэтры частенько его поправляют. Но VSL получился действительно простым и я решил что автор вряд-ли сумел там сильно напортачить. К тому-же это биндинг к Lua 5.3.0 (с поддержкой встроеного int64).
Тесты показали скорость примерно 160 миллисекунд на миллион вызовов.
Мне подходит.

Ответить

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 3 гостя