From 64ff027e645208c1a64a6dbeb2d1540da9e718f8 Mon Sep 17 00:00:00 2001 From: Niels Breuker Date: Sun, 2 Oct 2022 12:54:47 +0200 Subject: First PoC for multithreading in plugins. Crashes on reload while another thread is active. No nice way to return to the default lua_State --- src/Bindings/ManualBindings_Threading.cpp | 407 ++++++++++++++++++++++++++++++ 1 file changed, 407 insertions(+) create mode 100644 src/Bindings/ManualBindings_Threading.cpp (limited to 'src/Bindings/ManualBindings_Threading.cpp') diff --git a/src/Bindings/ManualBindings_Threading.cpp b/src/Bindings/ManualBindings_Threading.cpp new file mode 100644 index 000000000..3dde1f988 --- /dev/null +++ b/src/Bindings/ManualBindings_Threading.cpp @@ -0,0 +1,407 @@ + +// ManualBindings_Network.cpp + +// Implements the cNetwork-related API bindings for Lua +// Also implements the cUrlClient bindings + +#include "Globals.h" +#include "ManualBindings.h" +#include "tolua++/include/tolua++.h" +#include "LuaState.h" +#include +extern "C" +{ + #include + #include + #include +} + + + + +using LuaState = lua_State; + + + + +std::string lua_getstring(lua_State* L, int aIndex) +{ + size_t len; + auto s = lua_tolstring(L, aIndex, &len); + return std::string(s, len); +} + + + + + +std::string lua_getstringfield(lua_State* L, int aTableIndex, const char* aFieldName) +{ + lua_getfield(L, aTableIndex, aFieldName); + std::string res = lua_getstring(L, -1); + lua_pop(L, 1); + return res; +} + + + + + +int pushThreadIdOnLuaStack(lua_State* aState, const std::thread::id& aThreadId) +{ + std::stringstream ss; + ss << aThreadId; + auto str = ss.str(); + lua_pushlstring(aState, str.data(), str.size()); + return 1; +} + + + + +/** The name of the thread's Lua object's metatable within the Lua registry. +Every thread object that is pushed to the Lua side has the metatable of this name set to it. */ +static const char* THREAD_METATABLE_NAME = "std::thread *"; +static const char* MUTEX_METATABLE_NAME = "std::mutex *"; + + + + + +/** Dumps the contents of the Lua stack to the specified ostream. */ +static void dumpLuaStack(LuaState* L, std::ostream& aDest) +{ + aDest << "Lua stack contents:" << std::endl; + for (int i = lua_gettop(L); i >= 0; --i) + { + aDest << " " << i << "\t"; + aDest << lua_typename(L, lua_type(L, i)) << "\t"; + aDest << lua_getstring(L, i).c_str() << std::endl; + } + aDest << "(stack dump completed)" << std::endl; +} + + + + + +/** Dumps the call stack to the specified ostream. */ +static void dumpLuaTraceback(LuaState* L, std::ostream& aDest) +{ + + + //luaL_traceback(L, L, "Stack trace: ", 0); + aDest << lua_getstring(L, -1).c_str() << std::endl; + lua_pop(L, 1); + return; +} + + + + + +/** Called by Lua when it encounters an unhandler error in the script file. */ +extern "C" int errorHandler(LuaState * L) +{ + auto err = lua_getstring(L, -1); + LOGWARNING(err); + //std::cerr << "Caught an error: " << err << std::endl; + //dumpLuaStack(L, std::cerr); + //exit(1); + return 0; +} + + + + + + +extern "C" static int mutexNew(LuaState * aState) +{ + cLuaState L(aState); + lua_pushcfunction(aState, errorHandler); + + // Push the (currently empty) mutex object to the Lua side + auto mutexObj = reinterpret_cast(lua_newuserdata(aState, sizeof(std::mutex**))); + L.Push(mutexObj); + //luaL_setmetatable(aState, MUTEX_METATABLE_NAME); + + // Create the new mutex: + *mutexObj = new std::mutex(); + return 1; +} + + + + + + + +/** */ +extern "C" static int mutexLock(LuaState * aState) +{ + auto mutexObj = reinterpret_cast(luaL_checkudata(aState, 1, MUTEX_METATABLE_NAME)); + if (mutexObj == nullptr) + { + luaL_argerror(aState, 0, "'mutex' expected"); + return 0; + } + (*mutexObj)->lock(); + auto numParams = lua_gettop(aState); + luaL_checktype(aState, 2, LUA_TFUNCTION); + lua_pcall(aState, 0, 0, 0); + (*mutexObj)->unlock(); + + return 0; +} + + + + + +static int threadNew(LuaState * aState) +{ + static std::recursive_mutex mtx; + std::scoped_lock lock(mtx); + luaL_checktype(aState, 1, LUA_TFUNCTION); + lua_pushvalue(aState, 1); // Push a copy of the fn to the top of the stack... + auto luaFnRef = luaL_ref(aState, LUA_REGISTRYINDEX); // ... move it to the registry... + auto luaThread = lua_newthread(aState); + lua_pushcfunction(aState, errorHandler); + lua_rawgeti(luaThread, LUA_REGISTRYINDEX, luaFnRef); // ... push it onto the new thread's stack... + luaL_unref(aState, LUA_REGISTRYINDEX, luaFnRef); // ... and remove it from the registry + + // Push the (currently empty) thread object to the Lua side + auto threadObj = reinterpret_cast(lua_newuserdata(aState, sizeof(std::thread**))); + //luaL_setmetatable(aState, THREAD_METATABLE_NAME); + + // Start the new thread: + *threadObj = new std::thread( + [luaThread, luaFnRef]() + { + auto numParams = lua_gettop(luaThread) - 1; + lua_call(luaThread, numParams, LUA_MULTRET); + //if (status == LUA_OK) + } + ); + cLuaState L(aState); + L.Push(threadObj); + return 1; +} + + + + + +/** Provides the thread.sleep() function. +Parameter: the number of seconds to sleep for (floating-point number). */ +extern "C" static int threadSleep(LuaState * aState) +{ + auto seconds = luaL_checknumber(aState, 1); + std::this_thread::sleep_for(std::chrono::milliseconds(static_cast(seconds * 1000))); + return 0; +} + + + + + +/** Implements the thread:join() function. +Joins the specified thread. +Errors if asked to join the current thread. */ +extern "C" static int threadObjJoin(LuaState * aState) +{ + auto threadObj = reinterpret_cast(luaL_checkudata(aState, 1, THREAD_METATABLE_NAME)); + if (threadObj == nullptr) + { + luaL_argerror(aState, 0, "`thread' expected"); + return 0; + } + if (*threadObj == nullptr) + { + luaL_argerror(aState, 0, "thread already joined"); + return 0; + } + if ((*threadObj)->get_id() == std::this_thread::get_id()) + { + luaL_argerror(aState, 0, "`thread' must not be the current thread"); + return 0; + } + (*threadObj)->join(); + *threadObj = nullptr; + return 0; +} + + + + + +/** Implements the thread:id() function. +Returns the thread's ID, as an implementation-dependent detail. +The ID is guaranteed to be unique within a single process at any single time moment (but not within multiple time moments). */ +extern "C" static int threadObjID(LuaState * aState) +{ + auto threadObj = reinterpret_cast(luaL_checkudata(aState, 1, THREAD_METATABLE_NAME)); + if (threadObj == nullptr) + { + luaL_argerror(aState, 0, "`thread' expected"); + return 0; + } + if (*threadObj == nullptr) + { + luaL_argerror(aState, 0, "thread already joined"); + return 0; + } + if ((*threadObj)->get_id() == std::this_thread::get_id()) + { + luaL_argerror(aState, 0, "`thread' must not be the current thread"); + return 0; + } + return pushThreadIdOnLuaStack(aState, (*threadObj)->get_id()); +} + + + + + +/** Implements the thread.currentid() function. +Returns the current thread's ID. This also works on the main thread. */ +extern "C" static int threadCurrentID(LuaState * aState) +{ + return pushThreadIdOnLuaStack(aState, std::this_thread::get_id()); +} + + + + + +/** Called when the Lua side GC's the thread object. +Joins the thread, if not already joined. */ +extern "C" static int threadObjGc(LuaState * aState) +{ + auto threadObj = reinterpret_cast(luaL_checkudata(aState, 1, THREAD_METATABLE_NAME)); + // We shouldn't get an invalid thread object, but let's check nevertheless: + if (threadObj == nullptr) + { + luaL_argerror(aState, 0, "`thread' expected"); + return 0; + } + if (*threadObj == nullptr) + { + return 0; + } + if ((*threadObj)->get_id() == std::this_thread::get_id()) + { + // Current thread is GC-ing self? No idea if that is allowed to happen, but we don't care; just don't join + return 0; + } + (*threadObj)->join(); + *threadObj = nullptr; + return 0; +} + + + + + +/** The functions in the thread library. */ +static const luaL_Reg threadFuncs[] = +{ + {"new", &threadNew}, + {"sleep", &threadSleep}, + {"currentid", &threadCurrentID}, + {NULL, NULL} +}; + + + + + +/** The functions of the thread object. */ +static const luaL_Reg threadObjFuncs[] = +{ + {"join", &threadObjJoin}, + {"id", &threadObjID}, + {"__gc", &threadObjGc}, + {NULL, NULL} +}; + + + + + +/***/ +static const luaL_Reg mutexFuncs[] = +{ + {"new", &mutexNew}, + {NULL,NULL} +}; + + + + + +/** */ +static const luaL_Reg mutexObjFuncs[] = +{ + {"lock", &mutexLock}, + {NULL, NULL} +}; + + + + + +/** Registers the thread library into the Lua VM. */ +extern "C" static int luaopen_thread(LuaState * aState) +{ + //luaL_newlib(aState, threadFuncs); + + //// Register the metatable for std::thread objects: + //luaL_newmetatable(aState, THREAD_METATABLE_NAME); + //lua_pushvalue(aState, -1); + //lua_setfield(aState, -2, "__index"); // metatable.__index = metatable + //luaL_setfuncs(aState, threadObjFuncs, 0); // Add the object functions to the table + //lua_pop(aState, 1); // pop the new metatable + + return 1; +} + + + + + +/**Registers the mutex library into the Lua VM. */ +extern "C" static int luaopen_mutex(LuaState * aState) +{ + //luaL_newlib(aState, mutexFuncs); + + //// Register the metatable for std::thread objects: + //luaL_newmetatable(aState, MUTEX_METATABLE_NAME); + //lua_pushvalue(aState, -1); + //lua_setfield(aState, -2, "__index"); // metatable.__index = metatable + //luaL_setfuncs(aState, mutexObjFuncs, 0); // Add the object functions to the table + //lua_pop(aState, 1); // pop the new metatable + + return 1; +} + + + + + +void cManualBindings::BindThreading(lua_State * tolua_S) +{ + tolua_usertype(tolua_S, "cThread"); + tolua_cclass(tolua_S, "cThread", "cThread", "", nullptr); + tolua_beginmodule(tolua_S, nullptr); + tolua_beginmodule(tolua_S, "cThread"); + tolua_function(tolua_S, "new", threadNew); + tolua_function(tolua_S, "sleep", threadSleep); + tolua_function(tolua_S, "currentid", threadCurrentID); + tolua_function(tolua_S, "join", threadObjJoin); + tolua_function(tolua_S, "id", threadObjID); + /*tolua_function(tolua_S, threadObjFuncs)*/ + tolua_endmodule(tolua_S); + tolua_endmodule(tolua_S); +} -- cgit v1.2.3