Несколько скриптов в одном состоянии Lua и работа с _ENV
В настоящее время я изучаю, как использовать API Lua C, и, хотя у меня были успешные функции связывания между C/C++ и Lua, у меня есть несколько вопросов:
Это хорошая идея, чтобы загрузить несколько сценариев в один
lua_State
? Есть ли способ закрыть определенные куски? Если скрипт больше не используется, как я могу очистить его отlua_State
сохраняя все остальное?Как лучше всего использовать скрипты, которые могут использовать одно и то же имя для функций / глобальных переменных? Если я загружу их все, более новые определения переопределяют более старые.
После чтения в Интернете я думаю, что мне нужно разделить каждый загруженный блок на разные среды. Я предполагаю, что при такой работе каждый раз при загрузке чанка я присваиваю ему уникальное имя среды, когда мне нужно с ним работать, я просто использую это имя для извлечения среды из
LUA_REGISTRYINDEX
и выполнить операцию. До сих пор я не понял, как это сделать. В Интернете есть примеры, но они используют Lua 5.1.
3 ответа
Поработав еще немного, я нашел то, что мне кажется решением, которое я искал. Я не уверен, что это правильный / лучший способ сделать это, но он работает в моем базовом тестовом примере. Ответ @jpjacobs на этот вопрос очень помог.
test1.lua
x = 1
function hi()
print("hi1");
print(x);
end
hi()
test2.lua
x =2
function hi()
print("hi2");
print(x);
end
hi()
main.cpp
int main(void)
{
lua_State* L = luaL_newstate();
luaL_openlibs(L);
char* file1 = "Rooms/test1.lua";
char* file2 = "Rooms/test2.lua";
//We load the file
luaL_loadfile(L, file1);
//Create _ENV tables
lua_newtable(L);
//Create metatable
lua_newtable(L);
//Get the global table
lua_getglobal(L, "_G");
lua_setfield(L, -2, "__index");
//Set global as the metatable
lua_setmetatable(L, -2);
//Push to registry with a unique name.
//I feel like these 2 steps could be merged or replaced but I'm not sure how
lua_setfield(L, LUA_REGISTRYINDEX, "test1");
//Retrieve it.
lua_getfield(L, LUA_REGISTRYINDEX, "test1");
//Set the upvalue (_ENV)
lua_setupvalue(L, 1, 1);
//Run chunks
lua_pcall(L, 0, LUA_MULTRET, 0);
//Repeat
luaL_loadfile(L, file2);
lua_newtable(L);
lua_newtable(L);
lua_getglobal(L, "_G");
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
lua_setfield(L, LUA_REGISTRYINDEX, "test2");
lua_getfield(L, LUA_REGISTRYINDEX, "test2");
lua_setupvalue(L, 1, 1);
lua_pcall(L, 0, LUA_MULTRET, 0);
//Retrieve the table containing the functions of the chunk
lua_getfield(L, LUA_REGISTRYINDEX, "test1");
//Get the function we want to call
lua_getfield(L, -1, "hi");
//Call it
lua_call(L, 0, 0);
//Repeat
lua_getfield(L, LUA_REGISTRYINDEX, "test2");
lua_getfield(L, -1, "hi");
lua_call(L, 0, 0);
lua_getfield(L, LUA_REGISTRYINDEX, "test2");
lua_getfield(L, -1, "hi");
lua_call(L, 0, 0);
lua_getfield(L, LUA_REGISTRYINDEX, "test1");
lua_getfield(L, -1, "hi");
lua_call(L, 0, 0);
lua_close(L);
}
Выход:
hi1
1
hi2
2
hi1
1
hi2
2
hi2
2
hi1
1
Я использую Lua 5.3.2 с Visual Studio 2013, если это что-то значит.
Этот базовый тестовый пример работает по мере необходимости. Я продолжу тестирование, чтобы увидеть, появятся ли какие-либо проблемы / улучшения. Если кто-нибудь видит какой-либо способ, которым я мог бы улучшить этот код или и вопиющие ошибки, пожалуйста, оставьте комментарий.
Это хорошая идея, чтобы загрузить несколько сценариев в одном lua_State?
Определенно да. Если эти сценарии не связаны и должны выполняться в нескольких параллельных потоках.
Есть ли способ закрыть определенные куски?
Чанк - это просто значение типа "функция". Если вы не храните это значение в любом месте - чанк будет собираться мусором.
Все, что произойдет - глобалы или местные жители, у которых есть ссылки где-то за пределами - те будут жить.
как очистить его от lua_State при сохранении всего остального?
Это зависит от того, как вы видите этот кусок. Является ли это просто набором функций или представляет некоторую сущность с собственным состоянием. Если вы не создадите глобальные функции и переменные, то все, что определено в отдельном файле скрипта, будет локальным для чанка и будет удалено, когда не останется ссылок на чанк.
Как лучше всего использовать скрипты, которые могут использовать одно и то же имя для функций / глобальных переменных?
Попробуйте переписать ваш код. Не создавайте глобальные переменные, если только это явно не требуется для создания связи с другими частями вашей программы. Сделайте переменные локальными (принадлежащими чанку) или сохраните их в таблице / закрытии, которые будут возвращены чанком как новый объект - чанк может быть фабрикой, производящей новые объекты, а не блокировать сценарий.
Также Lua работает быстрее с локальными переменными.
То, как я представляю эту работу, заключается в том, что при каждой загрузке чанка я присваиваю ему уникальное имя среды
Это следует делать, если сценарии поступают извне - написаны пользователями или получены из других внешних источников. Песочница - это круто, но нет необходимости в песочнице, если куски - это ваш внутренний материал. Рассмотрим переписывание кода без глобалов. Верните некоторый объект (таблицу API или замыкание), если ваш чанк производит другие объекты - вы можете вызывать этот чанк много раз, не перезагружая его. Или сохраните один глобальный интерфейс модуля, если блок представляет собой Lua-подобный модуль. Если вы плохо организовали свой код, то вам придется использовать отдельные среды, и вам придется подготовить новую среду для каждого сценария, скопировать основные вещи, такие как print / pair / string / etc. У вас будет много перерывов во время выполнения, пока вы не поймете, чего еще не хватает в новой среде и так далее.
Вы должны рассматривать каждый из ваших скриптов как отдельный модуль. точно так же, как у вас больше, чем 1 "требуют" в вашем коде.
ваш "загруженный кусок" должен возвращать таблицу, которая будет храниться глобально.
загружать множество глобальных переменных не очень хорошая идея, это может привести к плохим последствиям после добавления новых модулей.