mirror of
https://github.com/cuberite/cuberite.git
synced 2025-01-08 11:57:39 +08:00
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:
parent
c61dab94bd
commit
64ff027e64
@ -17,6 +17,7 @@ target_sources(
|
||||
ManualBindings_Network.cpp
|
||||
ManualBindings_RankManager.cpp
|
||||
ManualBindings_World.cpp
|
||||
ManualBindings_Threading.cpp
|
||||
Plugin.cpp
|
||||
PluginLua.cpp
|
||||
PluginManager.cpp
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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:
|
||||
|
407
src/Bindings/ManualBindings_Threading.cpp
Normal file
407
src/Bindings/ManualBindings_Threading.cpp
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user