Несколько скриптов в одном состоянии Lua и работа с _ENV

В настоящее время я изучаю, как использовать API Lua C, и, хотя у меня были успешные функции связывания между C/C++ и Lua, у меня есть несколько вопросов:

  1. Это хорошая идея, чтобы загрузить несколько сценариев в один lua_State? Есть ли способ закрыть определенные куски? Если скрипт больше не используется, как я могу очистить его от lua_State сохраняя все остальное?

  2. Как лучше всего использовать скрипты, которые могут использовать одно и то же имя для функций / глобальных переменных? Если я загружу их все, более новые определения переопределяют более старые.

    После чтения в Интернете я думаю, что мне нужно разделить каждый загруженный блок на разные среды. Я предполагаю, что при такой работе каждый раз при загрузке чанка я присваиваю ему уникальное имя среды, когда мне нужно с ним работать, я просто использую это имя для извлечения среды из 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 "требуют" в вашем коде.

ваш "загруженный кусок" должен возвращать таблицу, которая будет храниться глобально.

загружать множество глобальных переменных не очень хорошая идея, это может привести к плохим последствиям после добавления новых модулей.

Другие вопросы по тегам