First PoC for multithreading in plugins.

Crashes on reload while another thread is active.
No nice way to return to the default lua_State
This commit is contained in:
Niels Breuker 2022-10-02 12:54:47 +02:00
parent c61dab94bd
commit 64ff027e64
6 changed files with 437 additions and 2 deletions

View File

@ -17,6 +17,7 @@ target_sources(
ManualBindings_Network.cpp
ManualBindings_RankManager.cpp
ManualBindings_World.cpp
ManualBindings_Threading.cpp
Plugin.cpp
PluginLua.cpp
PluginManager.cpp

View File

@ -1033,6 +1033,16 @@ void cLuaState::Push(cLuaTCPLink * a_TCPLink)
void cLuaState::Push(std::thread* a_Thread) {
ASSERT(IsValid());
tolua_pushusertype(m_LuaState, a_Thread, "std::thread *");
}
void cLuaState::Push(cLuaUDPEndpoint * a_UDPEndpoint)
{
ASSERT(IsValid());
@ -1099,6 +1109,17 @@ void cLuaState::Push(std::chrono::milliseconds a_Value)
void cLuaState::Push(std::mutex * a_Mutex) {
ASSERT(IsValid());
tolua_pushusertype(m_LuaState, a_Mutex, "std::mutex *");
}
void cLuaState::Pop(int a_NumValuesToPop)
{
ASSERT(IsValid());

View File

@ -624,12 +624,14 @@ public:
void Push(const cEntity * a_Entity);
void Push(cLuaServerHandle * a_ServerHandle);
void Push(cLuaTCPLink * a_TCPLink);
void Push(std::thread * a_Thread);
void Push(cLuaUDPEndpoint * a_UDPEndpoint);
void Push(double a_Value);
void Push(int a_Value);
void Push(long a_Value);
void Push(const UInt32 a_Value);
void Push(std::chrono::milliseconds a_time);
void Push(std::mutex * a_Mutex);
/** Pops the specified number of values off the top of the Lua stack. */
void Pop(int a_NumValuesToPop = 1);

View File

@ -1447,12 +1447,12 @@ static int tolua_cPluginManager_CallPlugin(lua_State * tolua_S)
{
return 0;
}
if (ThisPlugin->GetName() == PluginName)
/*if (ThisPlugin->GetName() == PluginName)
{
LOGWARNING("cPluginManager::CallPlugin(): Calling self is not implemented (why would it?)");
L.LogStackTrace();
return 0;
}
}*/
// Call the destination plugin using a plugin callback:
int NumReturns = 0;
@ -4768,6 +4768,7 @@ void cManualBindings::Bind(lua_State * tolua_S)
BindRankManager(tolua_S);
BindWorld(tolua_S);
BindBlockArea(tolua_S);
BindThreading(tolua_S);
tolua_endmodule(tolua_S);
}

View File

@ -46,6 +46,9 @@ protected:
Implemented in ManualBindings_BlockArea.cpp. */
static void BindBlockArea(lua_State * tolua_S);
/** Binds the manually implemented threading API */
static void BindThreading(lua_State * tolua_S);
public:
// Helper functions:

View File

@ -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 <sstream>
extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
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<std::mutex**>(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<std::mutex**>(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<std::thread**>(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<int>(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<std::thread**>(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<std::thread**>(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<std::thread**>(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);
}