I got problem when making a class to utilize multitasking system in Lua. This class should enable run multiple function at same time by use of Lua's coroutine system.
I'd like to write the ideal usage of the class in both host code and Lua code.
IN HOST CODE
int main(void)
{
//Make state.
auto L{luaL_newstate()};
luaL_openlibs(L);
//Enable multitasking.
TaskSystem tasks{L};
if (luaL_dofile(L, "script.lua") != 0)
{
auto msg{lua_tostring(L, -1)};
std::cerr << msg << std::endl;
}
//Consider this as main loop of games.
while (true)
{
tasks.resume_all();
std::cin.get();
}
lua_close(L);
}
IN LUA CODE
--30 tasks run parallel.
--'run_task' creates coroutine from the function given,
-- and registers it to the list of coroutines in the host code.
for i = 0, 30 do
run_task
(
function()
while true do
print(i)
coroutine.yield()
end
end
)
end
I expect the example I show to run functions given to 'run_task' in Lua code as if they are in multitasking system.
Then I show the implementation of 'class TaskSystem'. Sorry, this code is a bit long.
#include <list>
#include <boost/range/algorithm_ext/erase.hpp>
class TaskSystem
{
private:
lua_State* main_thread;
struct Task
{
lua_State* thread;
ThreadStatus status;
bool to_continue(void) const {return status == LUA_YIELD; }
};
using TaskList = std::list<Task>;
TaskList task_list;
static int run_task(lua_State* _parent)
{
//coroutine.create(func)
lua_getglobal(_parent, "coroutine");
lua_getfield(_parent, -1, "create");
lua_pushvalue(_parent, 1); //argument
lua_pcall(_parent, 1, 1, 0);
//Converte coroutine into thread object.
auto co{lua_tothread(_parent, -1)};
lua_settop(_parent, 0);
//Register the coroutine.
auto& task_list{*static_cast<TaskList*>(lua_touserdata(_parent, lua_upvalueindex(1)))};
task_list.push_back({co, lua_resume(co, nullptr, 0)});
return 0;
}
public:
TaskSystem(void) :main_thread(nullptr), task_list(){}
//Enable multitasking.
TaskSystem(lua_State* _main_thread)
{
initialize(_main_thread);
}
//Enable multitasiking in the thread.
void initialize(lua_State* _main_thread)
{
if (!_main_thread)
{
throw std::runtime_error("TaskSystem must be initialized by valid thread.");
}
main_thread = _main_thread;
lua_pushlightuserdata(main_thread, &task_list); //Task list as upvalue.
lua_pushcclosure(main_thread, &run_task, 1);
lua_setglobal(main_thread, "run_task");
}
//Resume all tasks.
void resume_all(void)
{
//Remove all threads not resumable.
//'boost::remove_erase_if' is the function splicing the container(1st arg) according to the lambda(2nd arg).
boost::remove_erase_if(task_list, [](const Task& _t) {return !_t.to_continue(); });
//Resume all thread on the list.
for (auto& t : task_list)
{
if (t.to_continue())
{
t.status = lua_resume(t.thread, nullptr, 0);
}
}
}
//Return the number of the tasks.
std::size_t count(void) const { return task_list.size(); }
};
The problem confusing me is the unknown exception thrown by 'TaskSystem::resume_all' when you try to run this code.
The exception has tendency to be thrown when the number of tasks is 24 or larger.
I once thought stackoverflow happened in Lua environment, but the exception thrown even if I extend the stack size by 'lua_checkstack'.
If someone notice the resolution, please help me.
My environment
Lua version: 5.2.4
IDE/compiler: Microsoft visual studio community 2015
Host code language: C++14
REVISED CODE
class TaskSystem
{
private:
using ThreadRef = decltype(luaL_ref(nullptr, 0));
lua_State* main_thread;
struct Task
{
ThreadRef thread;
ThreadStatus status;
bool to_continue(void) const {return status == LUA_YIELD; }
};
using TaskList = std::list<Task>;
TaskList task_list;
static int run_task(lua_State* _parent)
{
//coroutine.create(func)
lua_getglobal(_parent, "coroutine");
lua_getfield(_parent, -1, "create");
lua_pushvalue(_parent, 1); //argument
lua_pcall(_parent, 1, 1, 0);
//co is reference to the thread object.
auto co{luaL_ref(_parent, LUA_REGISTRYINDEX)};
//Register the coroutine.
auto& task_list{*static_cast<TaskList*>(lua_touserdata(_parent, lua_upvalueindex(1)))};
task_list.push_back({co, LUA_YIELD});
return 0;
}
public:
TaskSystem(void) :main_thread(nullptr), task_list(){}
//Enable multitasking.
TaskSystem(lua_State* _main_thread)
{
initialize(_main_thread);
}
//Enable multitasiking in the thread.
void initialize(lua_State* _main_thread)
{
if (!_main_thread)
{
throw std::runtime_error("TaskSystem must be initialized by valid thread.");
}
main_thread = _main_thread;
lua_pushlightuserdata(main_thread, &task_list); //Task list as upvalue.
lua_pushcclosure(main_thread, &run_task, 1);
lua_setglobal(main_thread, "run_task");
}
//Resume all tasks.
void resume_all(void)
{
//Remove all threads not resumable.
boost::remove_erase_if(task_list, [](const Task& _t) {return !_t.to_continue(); });
//Resume all thread on the list.
for (auto& t : task_list)
{
//Get thread object,
lua_rawgeti(main_thread, LUA_REGISTRYINDEX, t.thread);
auto thread{ lua_tothread(main_thread, -1) };
//and call it.
t.status = lua_resume(thread, nullptr, 0);
//Disable referenct to the thread if its life-time ended.
if(!t.to_continue()){luaL_unref(main_thread, LUA_REGISTRYINDEX, t.thread);}
}
}
//Return the number of the tasks.
std::size_t count(void) const { return task_list.size(); }
}