diff --git a/Client/mods/deathmatch/StdInc.h b/Client/mods/deathmatch/StdInc.h index 3c5017488aa..94f6aa689b0 100644 --- a/Client/mods/deathmatch/StdInc.h +++ b/Client/mods/deathmatch/StdInc.h @@ -140,6 +140,7 @@ #include #include #include +#include #include // Shared includes diff --git a/Client/mods/deathmatch/logic/lua/CLuaArguments.cpp b/Client/mods/deathmatch/logic/lua/CLuaArguments.cpp index c93cc86bb4b..c599ae3932a 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaArguments.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaArguments.cpp @@ -314,6 +314,68 @@ bool CLuaArguments::CallGlobal(CLuaMain* pLuaMain, const char* szFunction, CLuaA return true; } +bool CLuaArguments::CallGlobal(lua_State* luaVM, const char* szFunction, CLuaArguments* returnValues) const +{ + assert(szFunction); + TIMEUS startTime = GetTimeUs(); + + // Add the function name to the stack and get the event from the table + assert(luaVM); + LUA_CHECKSTACK(luaVM, 1); + int luaStackPointer = lua_gettop(luaVM); + lua_pushstring(luaVM, szFunction); + lua_gettable(luaVM, LUA_GLOBALSINDEX); + + // If that function doesn't exist, return false + if (lua_isnil(luaVM, -1)) + { + // cleanup the stack + while (lua_gettop(luaVM) - luaStackPointer > 0) + lua_pop(luaVM, 1); + + return false; + } + + // Push our arguments onto the stack + PushArguments(luaVM); + + // Reset function call timer (checks long-running functions) + // pLuaMain->ResetInstructionCount(); + + // Call the function with our arguments + int iret = lua_pcall(luaVM, m_Arguments.size(), LUA_MULTRET, 0); + if (iret == LUA_ERRRUN || iret == LUA_ERRMEM) + { + std::string strRes = ConformResourcePath(lua_tostring(luaVM, -1)); + g_pClientGame->GetScriptDebugging()->LogPCallError(luaVM, strRes); + + // cleanup the stack + while (lua_gettop(luaVM) - luaStackPointer > 0) + lua_pop(luaVM, 1); + + return false; // the function call failed + } + else + { + int iReturns = lua_gettop(luaVM) - luaStackPointer; + + if (returnValues != NULL) + { + for (int i = -iReturns; i <= -1; i++) + { + returnValues->ReadArgument(luaVM, i); + } + } + + // cleanup the stack + while (lua_gettop(luaVM) - luaStackPointer > 0) + lua_pop(luaVM, 1); + } + + // CPerfStatLuaTiming::GetSingleton()->UpdateLuaTiming(pLuaMain, szFunction, GetTimeUs() - startTime); + return true; +} + CLuaArgument* CLuaArguments::PushNil() { CLuaArgument* pArgument = new CLuaArgument; diff --git a/Client/mods/deathmatch/logic/lua/CLuaArguments.h b/Client/mods/deathmatch/logic/lua/CLuaArguments.h index 89011983f20..b49f6f0a001 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaArguments.h +++ b/Client/mods/deathmatch/logic/lua/CLuaArguments.h @@ -48,6 +48,7 @@ class CLuaArguments void PushArguments(const CLuaArguments& Arguments); bool Call(class CLuaMain* pLuaMain, const CLuaFunctionRef& iLuaFunction, CLuaArguments* returnValues = NULL) const; bool CallGlobal(class CLuaMain* pLuaMain, const char* szFunction, CLuaArguments* returnValues = NULL) const; + bool CallGlobal(lua_State* luaVM, const char* szFunction, CLuaArguments* returnValues) const; void ReadTable(lua_State* luaVM, int iIndexBegin, CFastHashMap* pKnownTables = NULL); void PushAsTable(lua_State* luaVM, CFastHashMap* pKnownTables = nullptr) const; diff --git a/Client/mods/deathmatch/logic/lua/CLuaCFunctions.cpp b/Client/mods/deathmatch/logic/lua/CLuaCFunctions.cpp index 3f7301d0a79..8571c36a9ca 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaCFunctions.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaCFunctions.cpp @@ -11,6 +11,8 @@ #include static std::vector m_sFunctions; +static std::vector m_ThreadSafeFunctions; +static std::vector m_ThreadFunctions; static std::map ms_Functions; static void* ms_pFunctionPtrLow = (void*)0xffffffff; static void* ms_pFunctionPtrHigh = 0; @@ -133,6 +135,30 @@ void CLuaCFunctions::RegisterFunctionsWithVM(lua_State* luaVM) } } +void CLuaCFunctions::RegisterThreadSafeFunctionsWithVM(lua_State* luaVM) +{ + // Register all our functions to a lua VM + std::vector::const_iterator iter = m_ThreadSafeFunctions.begin(); + for (; iter != m_ThreadSafeFunctions.end(); iter++) + { + lua_pushstring(luaVM, (*iter)->GetFunctionName()); + lua_pushcclosure(luaVM, (*iter)->GetFunctionAddress(), 1); + lua_setglobal(luaVM, (*iter)->GetFunctionName()); + } +} + +void CLuaCFunctions::RegisterThreadFunctionsWithVM(lua_State* luaVM) +{ + // Register all our functions to a lua VM + std::vector::const_iterator iter = m_ThreadFunctions.begin(); + for (; iter != m_ThreadFunctions.end(); iter++) + { + lua_pushstring(luaVM, (*iter)->GetFunctionName()); + lua_pushcclosure(luaVM, (*iter)->GetFunctionAddress(), 1); + lua_setglobal(luaVM, (*iter)->GetFunctionName()); + } +} + void CLuaCFunctions::RemoveAllFunctions() { for (CLuaCFunction* luaCFunction : m_sFunctions) diff --git a/Client/mods/deathmatch/logic/lua/CLuaCFunctions.h b/Client/mods/deathmatch/logic/lua/CLuaCFunctions.h index 29f20f7a400..fa5427410e4 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaCFunctions.h +++ b/Client/mods/deathmatch/logic/lua/CLuaCFunctions.h @@ -48,6 +48,8 @@ class CLuaCFunctions static bool IsRestricted(const char* szName); static void RegisterFunctionsWithVM(lua_State* luaVM); + static void RegisterThreadSafeFunctionsWithVM(lua_State* luaVM); + static void RegisterThreadFunctionsWithVM(lua_State* luaVM); static void RemoveAllFunctions(); }; diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h index 1181adcd0de..87ca18a6933 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h @@ -460,6 +460,10 @@ inline SString GetClassByTypeName(eClientModelType) { return "client-model-type"; } +inline SString GetClassTypeName(CLuaThread*) +{ + return "lua-thread"; +} // // CResource from userdata diff --git a/Client/mods/deathmatch/logic/lua/CLuaMain.cpp b/Client/mods/deathmatch/logic/lua/CLuaMain.cpp index 4f97dc44ba4..7844ef78a84 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaMain.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaMain.cpp @@ -34,6 +34,7 @@ CLuaMain::CLuaMain(CLuaManager* pLuaManager, CResource* pResourceOwner, bool bEn m_luaVM = NULL; m_bBeingDeleted = false; m_pLuaTimerManager = new CLuaTimerManager; + m_pLuaThreadManager = new CLuaThreadManager; m_FunctionEnterTimer.SetMaxIncrement(500); m_pResource = pResourceOwner; @@ -58,6 +59,9 @@ CLuaMain::~CLuaMain() // Delete the timer manager delete m_pLuaTimerManager; + // Delete the thread manager + delete m_pLuaThreadManager; + CClientPerfStatLuaMemory::GetSingleton()->OnLuaMainDestroy(this); CClientPerfStatLuaTiming::GetSingleton()->OnLuaMainDestroy(this); } @@ -72,7 +76,7 @@ void CLuaMain::ResetInstructionCount() m_FunctionEnterTimer.Reset(); } -void CLuaMain::InitSecurity() +void CLuaMain::InitSecurity(lua_State* luaVM) { // Disable dangerous Lua Os library functions static const luaL_reg osfuncs[] = @@ -86,14 +90,14 @@ void CLuaMain::InitSecurity() { "setlocale", CLuaUtilDefs::DisabledFunction }, { NULL, NULL } }; - luaL_register(m_luaVM, "os", osfuncs); - - lua_register(m_luaVM, "dofile", CLuaUtilDefs::DisabledFunction); - lua_register(m_luaVM, "loadfile", CLuaUtilDefs::DisabledFunction); - lua_register(m_luaVM, "require", CLuaUtilDefs::DisabledFunction); - lua_register(m_luaVM, "loadlib", CLuaUtilDefs::DisabledFunction); - lua_register(m_luaVM, "getfenv", CLuaUtilDefs::DisabledFunction); - lua_register(m_luaVM, "newproxy", CLuaUtilDefs::DisabledFunction); + luaL_register(luaVM, "os", osfuncs); + + lua_register(luaVM, "dofile", CLuaUtilDefs::DisabledFunction); + lua_register(luaVM, "loadfile", CLuaUtilDefs::DisabledFunction); + lua_register(luaVM, "require", CLuaUtilDefs::DisabledFunction); + lua_register(luaVM, "loadlib", CLuaUtilDefs::DisabledFunction); + lua_register(luaVM, "getfenv", CLuaUtilDefs::DisabledFunction); + lua_register(luaVM, "newproxy", CLuaUtilDefs::DisabledFunction); } void CLuaMain::InitClasses(lua_State* luaVM) @@ -159,7 +163,7 @@ void CLuaMain::InitVM() luaopen_os(m_luaVM); // Initialize security restrictions. Very important to prevent lua trojans and viruses! - InitSecurity(); + InitSecurity(m_luaVM); // Register module functions CLuaCFunctions::RegisterFunctionsWithVM(m_luaVM); diff --git a/Client/mods/deathmatch/logic/lua/CLuaMain.h b/Client/mods/deathmatch/logic/lua/CLuaMain.h index 778564ddbeb..a8627679bf9 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaMain.h +++ b/Client/mods/deathmatch/logic/lua/CLuaMain.h @@ -14,10 +14,12 @@ class CLuaMain; #pragma once #include "CLuaTimerManager.h" +#include "./../Shared/mods/deathmatch/logic/lua/CLuaThreadManager.h" #include "lua/CLuaVector2.h" #include "lua/CLuaVector3.h" #include "lua/CLuaVector4.h" #include "lua/CLuaMatrix.h" +#include "lua/CLuaThread.h" #include "CLuaFunctionDefs.h" @@ -26,6 +28,7 @@ class CLuaMain; #define MAX_SCRIPTNAME_LENGTH 64 #include +CLuaThreadManager* a = new CLuaThreadManager(); struct CRefInfo { @@ -51,8 +54,9 @@ class CLuaMain //: public CClient const char* GetScriptName() const { return m_strScriptName; } void SetScriptName(const char* szName) { m_strScriptName.AssignLeft(szName, MAX_SCRIPTNAME_LENGTH); } - lua_State* GetVM() { return m_luaVM; }; - CLuaTimerManager* GetTimerManager() const { return m_pLuaTimerManager; }; + lua_State* GetVM() { return m_luaVM; }; + CLuaTimerManager* GetTimerManager() const { return m_pLuaTimerManager; }; + CLuaThreadManager* GetThreadManager() const { return m_pLuaThreadManager; }; bool BeingDeleted(); lua_State* GetVirtualMachine() const { return m_luaVM; }; @@ -79,15 +83,16 @@ class CLuaMain //: public CClient bool IsOOPEnabled() { return m_bEnableOOP; } + static void InitSecurity(lua_State* luaVM); private: - void InitSecurity(); static void InstructionCountHook(lua_State* luaVM, lua_Debug* pDebug); SString m_strScriptName; - lua_State* m_luaVM; - CLuaTimerManager* m_pLuaTimerManager; + lua_State* m_luaVM; + CLuaTimerManager* m_pLuaTimerManager; + CLuaThreadManager* m_pLuaThreadManager; bool m_bBeingDeleted; // prevent it being deleted twice diff --git a/Client/mods/deathmatch/logic/lua/LuaCommon.h b/Client/mods/deathmatch/logic/lua/LuaCommon.h index b240a82cd7b..16de830c6fa 100644 --- a/Client/mods/deathmatch/logic/lua/LuaCommon.h +++ b/Client/mods/deathmatch/logic/lua/LuaCommon.h @@ -45,6 +45,7 @@ class CClientPointLights; class CLuaTimer; class CResource; class CXMLNode; +class CLuaThread; // Lua push/pop macros for our datatypes @@ -62,6 +63,7 @@ void lua_pushvector(lua_State* luaVM, const CVector4D& vector); void lua_pushvector(lua_State* luaVM, const CVector& vector); void lua_pushvector(lua_State* luaVM, const CVector2D& vector); void lua_pushmatrix(lua_State* luaVM, const CMatrix& matrix); +void lua_pushluathread(lua_State* luaVM, CLuaThread* pThread); // Internal use void lua_initclasses(lua_State* luaVM); diff --git a/Server/mods/deathmatch/StdInc.h b/Server/mods/deathmatch/StdInc.h index ae05cbe4fd2..c4c1d9f74eb 100644 --- a/Server/mods/deathmatch/StdInc.h +++ b/Server/mods/deathmatch/StdInc.h @@ -142,6 +142,7 @@ struct SAclRequest; #include "luadefs/CLuaTeamDefs.h" #include "luadefs/CLuaTextDefs.h" #include "luadefs/CLuaTimerDefs.h" +#include "luadefs/CLuaThreadDefs.h" #include "luadefs/CLuaVehicleDefs.h" #include "luadefs/CLuaVoiceDefs.h" #include "luadefs/CLuaWaterDefs.h" @@ -157,6 +158,8 @@ struct SAclRequest; #include "lua/CLuaManager.h" #include "lua/CLuaTimerManager.h" #include "lua/CLuaTimer.h" +#include "lua/CLuaThreadManager.h" +#include "lua/CLuaThread.h" #include "lua/CLuaFunctionDefs.h" #include "lua/CLuaModuleManager.h" #include "lua/CLuaArgument.h" diff --git a/Server/mods/deathmatch/logic/lua/CLuaArguments.cpp b/Server/mods/deathmatch/logic/lua/CLuaArguments.cpp index 8c01812295d..e1ef3288b8a 100644 --- a/Server/mods/deathmatch/logic/lua/CLuaArguments.cpp +++ b/Server/mods/deathmatch/logic/lua/CLuaArguments.cpp @@ -310,6 +310,68 @@ bool CLuaArguments::CallGlobal(CLuaMain* pLuaMain, const char* szFunction, CLuaA return true; } +bool CLuaArguments::CallGlobal(lua_State* luaVM, const char* szFunction, CLuaArguments* returnValues) const +{ + assert(szFunction); + TIMEUS startTime = GetTimeUs(); + + // Add the function name to the stack and get the event from the table + assert(luaVM); + LUA_CHECKSTACK(luaVM, 1); + int luaStackPointer = lua_gettop(luaVM); + lua_pushstring(luaVM, szFunction); + lua_gettable(luaVM, LUA_GLOBALSINDEX); + + // If that function doesn't exist, return false + if (lua_isnil(luaVM, -1)) + { + // cleanup the stack + while (lua_gettop(luaVM) - luaStackPointer > 0) + lua_pop(luaVM, 1); + + return false; + } + + // Push our arguments onto the stack + PushArguments(luaVM); + + // Reset function call timer (checks long-running functions) + //pLuaMain->ResetInstructionCount(); + + // Call the function with our arguments + int iret = lua_pcall(luaVM, m_Arguments.size(), LUA_MULTRET, 0); + if (iret == LUA_ERRRUN || iret == LUA_ERRMEM) + { + std::string strRes = ConformResourcePath(lua_tostring(luaVM, -1)); + g_pGame->GetScriptDebugging()->LogPCallError(luaVM, strRes); + + // cleanup the stack + while (lua_gettop(luaVM) - luaStackPointer > 0) + lua_pop(luaVM, 1); + + return false; // the function call failed + } + else + { + int iReturns = lua_gettop(luaVM) - luaStackPointer; + + if (returnValues != NULL) + { + for (int i = -iReturns; i <= -1; i++) + { + returnValues->ReadArgument(luaVM, i); + } + } + + // cleanup the stack + while (lua_gettop(luaVM) - luaStackPointer > 0) + lua_pop(luaVM, 1); + } + + //CPerfStatLuaTiming::GetSingleton()->UpdateLuaTiming(pLuaMain, szFunction, GetTimeUs() - startTime); + return true; +} + CLuaArgument* CLuaArguments::PushNil() { CLuaArgument* pArgument = new CLuaArgument; @@ -428,6 +490,14 @@ CLuaArgument* CLuaArguments::PushTimer(CLuaTimer* pLuaTimer) return pArgument; } +CLuaArgument* CLuaArguments::PushThread(CLuaThread* pLuaThread) +{ + CLuaArgument* pArgument = new CLuaArgument; + pArgument->ReadScriptID(pLuaThread->GetScriptID()); + m_Arguments.push_back(pArgument); + return pArgument; +} + CLuaArgument* CLuaArguments::PushDbQuery(CDbJobData* pJobData) { CLuaArgument* pArgument = new CLuaArgument; diff --git a/Server/mods/deathmatch/logic/lua/CLuaArguments.h b/Server/mods/deathmatch/logic/lua/CLuaArguments.h index 10040c4fcf3..99e37e484f9 100644 --- a/Server/mods/deathmatch/logic/lua/CLuaArguments.h +++ b/Server/mods/deathmatch/logic/lua/CLuaArguments.h @@ -34,6 +34,7 @@ class CAccount; class CBan; class CElement; class CLuaTimer; +class CLuaThread; class CResource; class CTextDisplay; class CTextItem; @@ -60,6 +61,7 @@ class CLuaArguments void PushArguments(const CLuaArguments& Arguments); bool Call(class CLuaMain* pLuaMain, const CLuaFunctionRef& iLuaFunction, CLuaArguments* returnValues = NULL) const; bool CallGlobal(class CLuaMain* pLuaMain, const char* szFunction, CLuaArguments* returnValues = NULL) const; + bool CallGlobal(lua_State* luaVM, const char* szFunction, CLuaArguments* returnValues) const; void ReadTable(lua_State* luaVM, int iIndexBegin, CFastHashMap* pKnownTables = NULL); void PushAsTable(lua_State* luaVM, CFastHashMap* pKnownTables = nullptr) const; @@ -77,6 +79,7 @@ class CLuaArguments CLuaArgument* PushTextDisplay(CTextDisplay* pTextDisplay); CLuaArgument* PushTextItem(CTextItem* pTextItem); CLuaArgument* PushTimer(CLuaTimer* pLuaTimer); + CLuaArgument* PushThread(CLuaThread* pLuaThread); CLuaArgument* PushDbQuery(CDbJobData* pJobData); CLuaArgument* PushArgument(const CLuaArgument& argument); diff --git a/Server/mods/deathmatch/logic/lua/CLuaCFunctions.cpp b/Server/mods/deathmatch/logic/lua/CLuaCFunctions.cpp index 7733a492349..508ad3419be 100644 --- a/Server/mods/deathmatch/logic/lua/CLuaCFunctions.cpp +++ b/Server/mods/deathmatch/logic/lua/CLuaCFunctions.cpp @@ -13,6 +13,8 @@ CFastHashMap CLuaCFunctions::ms_Functions; CFastHashMap CLuaCFunctions::ms_FunctionsByName; +CFastHashMap CLuaCFunctions::ms_ThreadSafeFunctionsByName; +CFastHashMap CLuaCFunctions::ms_ThreadFunctionsByName; void* CLuaCFunctions::ms_pFunctionPtrLow = (void*)0xffffffff; void* CLuaCFunctions::ms_pFunctionPtrHigh = 0; @@ -42,7 +44,7 @@ CLuaCFunctions::~CLuaCFunctions() RemoveAllFunctions(); } -CLuaCFunction* CLuaCFunctions::AddFunction(const char* szName, lua_CFunction f, bool bRestricted) +CLuaCFunction* CLuaCFunctions::AddFunction(const char* szName, lua_CFunction f, bool bRestricted, EFunctionApplication eFunctionApplication) { ms_pFunctionPtrLow = std::min(ms_pFunctionPtrLow, (void*)f); ms_pFunctionPtrHigh = std::max(ms_pFunctionPtrHigh, (void*)f); @@ -59,7 +61,19 @@ CLuaCFunction* CLuaCFunctions::AddFunction(const char* szName, lua_CFunction f, pFunction = new CLuaCFunction(szName, f, bRestricted); ms_Functions[f] = pFunction; } - ms_FunctionsByName[szName] = pFunction; + + switch (eFunctionApplication) + { + case EFunctionApplication::MAIN_THREAD: + ms_FunctionsByName[szName] = pFunction; + break; + case EFunctionApplication::SHARED: + ms_ThreadSafeFunctionsByName[szName] = pFunction; + break; + case EFunctionApplication::WORKER_THREAD: + ms_ThreadFunctionsByName[szName] = pFunction; + break; + } return pFunction; } @@ -120,6 +134,30 @@ void CLuaCFunctions::RegisterFunctionsWithVM(lua_State* luaVM) } } +void CLuaCFunctions::RegisterThreadSafeFunctionsWithVM(lua_State* luaVM) +{ + // Register all our functions to a lua VM + CFastHashMap::iterator it; + for (it = ms_ThreadSafeFunctionsByName.begin(); it != ms_ThreadSafeFunctionsByName.end(); ++it) + { + lua_pushstring(luaVM, it->first.c_str()); + lua_pushcclosure(luaVM, it->second->GetAddress(), 1); + lua_setglobal(luaVM, it->first.c_str()); + } +} + +void CLuaCFunctions::RegisterThreadFunctionsWithVM(lua_State* luaVM) +{ + // Register all our functions to a lua VM + CFastHashMap::iterator it; + for (it = ms_ThreadFunctionsByName.begin(); it != ms_ThreadFunctionsByName.end(); ++it) + { + lua_pushstring(luaVM, it->first.c_str()); + lua_pushcclosure(luaVM, it->second->GetAddress(), 1); + lua_setglobal(luaVM, it->first.c_str()); + } +} + void CLuaCFunctions::RemoveAllFunctions() { // Delete all functions diff --git a/Server/mods/deathmatch/logic/lua/CLuaCFunctions.h b/Server/mods/deathmatch/logic/lua/CLuaCFunctions.h index 5ec62e4a543..cf9f8a075c3 100644 --- a/Server/mods/deathmatch/logic/lua/CLuaCFunctions.h +++ b/Server/mods/deathmatch/logic/lua/CLuaCFunctions.h @@ -15,6 +15,16 @@ class CLuaCFunctions; #include "LuaCommon.h" +enum class EFunctionApplication +{ + // Can be used only in main thread. Default + MAIN_THREAD, + // Thread safe + SHARED, + // Everywhere except in main thread + WORKER_THREAD +}; + class CLuaCFunction { public: @@ -39,19 +49,24 @@ class CLuaCFunctions CLuaCFunctions(); ~CLuaCFunctions(); - static CLuaCFunction* AddFunction(const char* szName, lua_CFunction f, bool bRestricted = false); + static CLuaCFunction* AddFunction(const char* szName, lua_CFunction f, bool bRestricted = false, + EFunctionApplication eFunctionApplication = EFunctionApplication::MAIN_THREAD); static void RemoveFunction(const SString& strName); static CLuaCFunction* GetFunction(lua_CFunction f); static CLuaCFunction* GetFunction(const char* szName); static bool IsNotFunction(lua_CFunction f); static void RegisterFunctionsWithVM(lua_State* luaVM); + static void RegisterThreadSafeFunctionsWithVM(lua_State* luaVM); + static void RegisterThreadFunctionsWithVM(lua_State* luaVM); static void RemoveAllFunctions(); private: static CFastHashMap ms_Functions; static CFastHashMap ms_FunctionsByName; + static CFastHashMap ms_ThreadSafeFunctionsByName; + static CFastHashMap ms_ThreadFunctionsByName; // functions availiable only in thread static void* ms_pFunctionPtrLow; static void* ms_pFunctionPtrHigh; }; diff --git a/Server/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp b/Server/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp index 5080411940a..a5faac189fc 100644 --- a/Server/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp +++ b/Server/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp @@ -290,6 +290,8 @@ SString GetUserDataClassName(void* ptr, lua_State* luaVM, bool bFindElementType) return GetClassTypeName(pVar); if (auto* pVar = UserDataCast((CLuaTimer*)NULL, ptr, luaVM)) // Try timer return GetClassTypeName(pVar); + if (auto* pVar = UserDataCast((CLuaThread*)NULL, ptr, luaVM)) // Try thread + return GetClassTypeName(pVar); if (auto* pVar = UserDataCast((CAccount*)NULL, ptr, luaVM)) return GetClassTypeName(pVar); if (auto* pVar = UserDataCast((CDbJobData*)NULL, ptr, luaVM)) diff --git a/Server/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h b/Server/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h index 0a6670474f7..dda9246307e 100644 --- a/Server/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h +++ b/Server/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h @@ -196,6 +196,10 @@ inline SString GetClassTypeName(CLuaTimer*) { return "lua-timer"; } +inline SString GetClassTypeName(CLuaThread*) +{ + return "lua-thread"; +} inline SString GetClassTypeName(CAccount*) { return "account"; @@ -277,6 +281,20 @@ CLuaTimer* UserDataCast(CLuaTimer*, void* ptr, lua_State* luaVM) return NULL; } +// +// CLuaThread from userdata +// +template +CLuaThread* UserDataCast(CLuaThread*, void* ptr, lua_State* luaVM) +{ + CLuaMain* pLuaMain = g_pGame->GetLuaManager()->GetVirtualMachine(luaVM); + if (pLuaMain) + { + return pLuaMain->GetThreadManager()->GetThreadFromScriptID(reinterpret_cast(ptr)); + } + return nullptr; +} + // // CAccount from userdata // diff --git a/Server/mods/deathmatch/logic/lua/CLuaMain.cpp b/Server/mods/deathmatch/logic/lua/CLuaMain.cpp index fa83d6b46d8..0426f25130e 100644 --- a/Server/mods/deathmatch/logic/lua/CLuaMain.cpp +++ b/Server/mods/deathmatch/logic/lua/CLuaMain.cpp @@ -37,6 +37,7 @@ CLuaMain::CLuaMain(CLuaManager* pLuaManager, CObjectManager* pObjectManager, CPl m_pResourceFile = NULL; m_bBeingDeleted = false; m_pLuaTimerManager = new CLuaTimerManager; + m_pLuaThreadManager = new CLuaThreadManager; m_FunctionEnterTimer.SetMaxIncrement(500); m_WarningTimer.SetMaxIncrement(1000); m_uiOpenFileCountWarnThresh = 10; @@ -71,6 +72,9 @@ CLuaMain::~CLuaMain() // Delete the timer manager delete m_pLuaTimerManager; + // Delete the thread manager + delete m_pLuaThreadManager; + // Eventually delete the XML files the LUA script didn't for (auto& xmlFile : m_XMLFiles) { @@ -105,7 +109,7 @@ void CLuaMain::ResetInstructionCount() m_FunctionEnterTimer.Reset(); } -void CLuaMain::InitSecurity() +void CLuaMain::InitSecurity(lua_State* luaVM) { // Disable dangerous Lua Os library functions static const luaL_reg osfuncs[] = @@ -119,17 +123,17 @@ void CLuaMain::InitSecurity() { "setlocale", CLuaUtilDefs::DisabledFunction }, { NULL, NULL } }; - luaL_register(m_luaVM, "os", osfuncs); + luaL_register(luaVM, "os", osfuncs); - lua_register(m_luaVM, "dofile", CLuaUtilDefs::DisabledFunction); - lua_register(m_luaVM, "loadfile", CLuaUtilDefs::DisabledFunction); - lua_register(m_luaVM, "require", CLuaUtilDefs::DisabledFunction); - lua_register(m_luaVM, "loadlib", CLuaUtilDefs::DisabledFunction); - lua_register(m_luaVM, "getfenv", CLuaUtilDefs::DisabledFunction); - lua_register(m_luaVM, "newproxy", CLuaUtilDefs::DisabledFunction); + lua_register(luaVM, "dofile", CLuaUtilDefs::DisabledFunction); + lua_register(luaVM, "loadfile", CLuaUtilDefs::DisabledFunction); + lua_register(luaVM, "require", CLuaUtilDefs::DisabledFunction); + lua_register(luaVM, "loadlib", CLuaUtilDefs::DisabledFunction); + lua_register(luaVM, "getfenv", CLuaUtilDefs::DisabledFunction); + lua_register(luaVM, "newproxy", CLuaUtilDefs::DisabledFunction); } -void CLuaMain::InitClasses(lua_State* luaVM) +void CLuaMain::InitClasses(lua_State* luaVM, bool bEnableOOP) { lua_initclasses(luaVM); lua_newclass(luaVM); @@ -141,7 +145,7 @@ void CLuaMain::InitClasses(lua_State* luaVM) CLuaMatrixDefs ::AddClass(luaVM); // OOP based classes - if (!m_bEnableOOP) + if (!bEnableOOP) return; CLuaElementDefs ::AddClass(luaVM); // keep this at the top because inheritance @@ -188,13 +192,13 @@ void CLuaMain::InitVM() luaopen_os(m_luaVM); // Initialize security restrictions. Very important to prevent lua trojans and viruses! - InitSecurity(); + InitSecurity(m_luaVM); // Registering C functions CLuaCFunctions::RegisterFunctionsWithVM(m_luaVM); // Create class metatables - InitClasses(m_luaVM); + InitClasses(m_luaVM, m_bEnableOOP); // Oli: Don't forget to add new ones to CLuaManager::LoadCFunctions. Thanks! @@ -361,6 +365,9 @@ void CLuaMain::UnloadScript() // Delete all timers and events m_pLuaTimerManager->RemoveAllTimers(); + // Delete all timers and events + m_pLuaThreadManager->RemoveAllThreads(); + // Delete all keybinds list::const_iterator iter = m_pPlayerManager->IterBegin(); for (; iter != m_pPlayerManager->IterEnd(); ++iter) @@ -382,6 +389,7 @@ void CLuaMain::UnloadScript() void CLuaMain::DoPulse() { m_pLuaTimerManager->DoPulse(this); + m_pLuaThreadManager->DoPulse(this); } // Keep count of the number of open files in this resource and issue a warning if too high diff --git a/Server/mods/deathmatch/logic/lua/CLuaMain.h b/Server/mods/deathmatch/logic/lua/CLuaMain.h index d8b4dcc3d72..49ed158b658 100644 --- a/Server/mods/deathmatch/logic/lua/CLuaMain.h +++ b/Server/mods/deathmatch/logic/lua/CLuaMain.h @@ -13,6 +13,7 @@ class CLuaMain; #pragma once #include "CLuaTimerManager.h" +#include "CLuaThreadManager.h" #include "lua/CLuaVector2.h" #include "lua/CLuaVector3.h" #include "lua/CLuaVector4.h" @@ -57,8 +58,9 @@ class CLuaMain //: public CClient const char* GetScriptName() const { return m_strScriptName; } void SetScriptName(const char* szName) { m_strScriptName.AssignLeft(szName, MAX_SCRIPTNAME_LENGTH); } - lua_State* GetVM() { return m_luaVM; }; - CLuaTimerManager* GetTimerManager() const { return m_pLuaTimerManager; }; + lua_State* GetVM() { return m_luaVM; }; + CLuaTimerManager* GetTimerManager() const { return m_pLuaTimerManager; }; + CLuaThreadManager* GetThreadManager() const { return m_pLuaThreadManager; }; CBlipManager* GetBlipManager() const { return m_pBlipManager; }; CObjectManager* GetObjectManager() const { return m_pObjectManager; }; @@ -109,9 +111,8 @@ class CLuaMain //: public CClient static int LuaLoadBuffer(lua_State* L, const char* buff, size_t sz, const char* name); static int OnUndump(const char* p, size_t n); -private: - void InitSecurity(); - void InitClasses(lua_State* luaVM); + static void InitSecurity(lua_State* luaVM); + static void InitClasses(lua_State* luaVM, bool bEnableOOP); public: bool IsOOPEnabled() { return m_bEnableOOP; } @@ -121,8 +122,9 @@ class CLuaMain //: public CClient SString m_strScriptName; - lua_State* m_luaVM; - CLuaTimerManager* m_pLuaTimerManager; + lua_State* m_luaVM; + CLuaTimerManager* m_pLuaTimerManager; + CLuaThreadManager* m_pLuaThreadManager; class CResource* m_pResource; class CResourceFile* m_pResourceFile; diff --git a/Server/mods/deathmatch/logic/lua/CLuaManager.cpp b/Server/mods/deathmatch/logic/lua/CLuaManager.cpp index d45cec6955e..fc356c1d320 100644 --- a/Server/mods/deathmatch/logic/lua/CLuaManager.cpp +++ b/Server/mods/deathmatch/logic/lua/CLuaManager.cpp @@ -319,6 +319,7 @@ void CLuaManager::LoadCFunctions() CLuaWaterDefs::LoadFunctions(); CLuaWorldDefs::LoadFunctions(); CLuaXMLDefs::LoadFunctions(); + CLuaThreadDefs::LoadFunctions(); // Backward compatibility functions at the end, so the new function name is used in ACL CLuaCompatibilityDefs::LoadFunctions(); } diff --git a/Server/mods/deathmatch/logic/lua/CLuaThread.cpp b/Server/mods/deathmatch/logic/lua/CLuaThread.cpp new file mode 100644 index 00000000000..d72f8e6b18b --- /dev/null +++ b/Server/mods/deathmatch/logic/lua/CLuaThread.cpp @@ -0,0 +1,177 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/lua/CLuaThread.cpp + * PURPOSE: Lua thread class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" + +#include "luascripts/coroutine_debug.lua.h" +#include "luascripts/exports.lua.h" +#include "luascripts/inspect.lua.h" + +CLuaThread::CLuaThread(const std::string& code) +{ + m_uiScriptID = CIdArray::PopUniqueId(this, EIdClass::THREAD); + m_strCode = code; + m_pAsyncTaskSheduler = std::make_unique(1); + SetState(EThreadState::INITIALIZING); + m_pAsyncTaskSheduler->PushTask( + [&] { + m_luaVM = lua_open(); + + luaopen_base(m_luaVM); + luaopen_math(m_luaVM); + luaopen_string(m_luaVM); + luaopen_table(m_luaVM); + luaopen_debug(m_luaVM); + luaopen_utf8(m_luaVM); + luaopen_os(m_luaVM); + + CLuaMain::InitSecurity(m_luaVM); + + // Registering C functions + CLuaCFunctions::RegisterThreadFunctionsWithVM(m_luaVM); + + int stackSize = lua_gettop(m_luaVM); + lua_pop(m_luaVM, stackSize); + + LoadUserCode(); + + return true; + }, + [&](bool success) { + if (success) + { + } + }); +} + +CLuaThread::~CLuaThread() +{ + Close(); + RemoveScriptID(); +} + +void CLuaThread::SetState(EThreadState state) +{ + std::lock_guard guard(m_lock); + m_eState = state; +} + +void CLuaThread::Close() +{ + if (m_luaVM) + { + lua_close(m_luaVM); + m_luaVM = nullptr; + } +} +void CLuaThread::LoadUserCode() +{ + int iResult = luaL_loadbuffer(m_luaVM, reinterpret_cast(m_strCode.c_str()), m_strCode.length(), "foo"); + if (iResult == LUA_ERRRUN || iResult == LUA_ERRMEM || iResult == LUA_ERRSYNTAX) + { + const char* result = lua_tostring(m_luaVM, -1); + m_strError = std::string(result); + SetState(EThreadState::FAILURE); + return; + } + + SetState(EThreadState::BUSY); + + int iret = lua_pcall(m_luaVM, 0, LUA_MULTRET, 0); + + CScriptArgReader argStream(m_luaVM); + { + std::lock_guard guard(m_lockReturnArguments); + argStream.ReadLuaArguments(m_returnArguments); + m_bHasReturnArguments = true; + } + + Idle(); + return; +} + +void CLuaThread::LoadScript(const char* code) +{ + // Run the script + if (!CLuaMain::LuaLoadBuffer(m_luaVM, code, strlen(code), NULL)) + { + int luaSavedTop = lua_gettop(m_luaVM); + int iret = lua_pcall(m_luaVM, 0, LUA_MULTRET, 0); + if (iret == LUA_ERRRUN || iret == LUA_ERRMEM) + { + std::string strRes = ConformResourcePath(lua_tostring(m_luaVM, -1)); + g_pGame->GetScriptDebugging()->LogPCallError(m_luaVM, strRes); + } + // Cleanup any return values + if (lua_gettop(m_luaVM) > luaSavedTop) + lua_settop(m_luaVM, luaSavedTop); + } + else + { + std::string strRes = ConformResourcePath(lua_tostring(m_luaVM, -1)); + g_pGame->GetScriptDebugging()->LogError(m_luaVM, "Loading in-line script failed: %s", strRes.c_str()); + } +} + +void CLuaThread::Idle() +{ + SetState(EThreadState::IDLE); +} + +void CLuaThread::DoPulse() +{ + m_pAsyncTaskSheduler->CollectResults(); +} + +void CLuaThread::Call(const std::string& functionName, const CLuaArguments& arguments, CLuaArguments& returns) +{ + SetState(EThreadState::BUSY); + arguments.CallGlobal(m_luaVM, functionName.c_str(), &returns); + Idle(); +} + +void CLuaThread::Call(const std::string& functionName, const CLuaArguments& arguments) +{ + m_pAsyncTaskSheduler->PushTask( + [&, arguments] { + CLuaArguments returns; + SetState(EThreadState::BUSY); + arguments.CallGlobal(m_luaVM, functionName.c_str(), &returns); + return true; + }, + [&](bool _) { Idle(); }); +} + +EThreadState CLuaThread::GetState() +{ + std::lock_guard guard(m_lock); + return m_eState; +} + +bool CLuaThread::GetReturnArguments(CLuaArguments& arguments) +{ + std::lock_guard guard(m_lockReturnArguments); + if (m_bHasReturnArguments) + { + arguments = m_returnArguments; + return true; + } + return false; +} + +void CLuaThread::RemoveScriptID() +{ + if (m_uiScriptID != INVALID_ARRAY_ID) + { + CIdArray::PushUniqueId(this, EIdClass::THREAD, m_uiScriptID); + m_uiScriptID = INVALID_ARRAY_ID; + } +} diff --git a/Server/mods/deathmatch/logic/lua/CLuaThread.h b/Server/mods/deathmatch/logic/lua/CLuaThread.h new file mode 100644 index 00000000000..6c8d92c73d7 --- /dev/null +++ b/Server/mods/deathmatch/logic/lua/CLuaThread.h @@ -0,0 +1,58 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/lua/CLuaThread.h + * PURPOSE: Lua thread class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +class CLuaThread; + +#pragma once + +// Define includes +#include "LuaCommon.h" +#include "CLuaArguments.h" + +class CLuaThread +{ +public: + CLuaThread(const std::string& code); + ~CLuaThread(); + + void RemoveScriptID(); + void DoPulse(); + void Idle(); + + uint GetScriptID() const { return m_uiScriptID; } + EThreadState GetState(); + void Call(const std::string& functionName, const CLuaArguments& arguments, CLuaArguments& returns); + void Call(const std::string& functionName, const CLuaArguments& arguments); + + bool GetReturnArguments(CLuaArguments& arguments); + +private: + void Close(); + + void SetState(EThreadState state); + void LoadScript(const char* code); + void LoadUserCode(); + + std::string m_strCode; + + std::mutex m_lockReturnArguments; + bool m_bHasReturnArguments = false; + CLuaArguments m_returnArguments; + + std::unique_ptr m_pAsyncTaskSheduler; + uint m_uiScriptID; + + EThreadState m_eState; + std::mutex m_lock; + lua_State* m_luaVM; + std::string m_strError; + bool m_bIsBusy; +}; diff --git a/Server/mods/deathmatch/logic/lua/CLuaThreadManager.cpp b/Server/mods/deathmatch/logic/lua/CLuaThreadManager.cpp new file mode 100644 index 00000000000..21e1e532a84 --- /dev/null +++ b/Server/mods/deathmatch/logic/lua/CLuaThreadManager.cpp @@ -0,0 +1,84 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/lua/CLuaThreadManager.cpp + * PURPOSE: Lua thread manager class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" + +void CLuaThreadManager::DoPulse(CLuaMain* pLuaMain) +{ + assert(m_ProcessQueue.empty()); + assert(!m_pPendingDelete); + assert(!m_pProcessingThread); + + CTickCount llCurrentTime = CTickCount::Now(); + + // Delete all the Threads + CFastList::const_iterator iter = m_ThreadList.begin(); + for (; iter != m_ThreadList.end(); ++iter) + { + (*iter)->DoPulse(); + } +} + +void CLuaThreadManager::RemoveThread(CLuaThread* pLuaThread) +{ + assert(pLuaThread); + + // Check if already removed + if (!ListContains(m_ThreadList, pLuaThread)) + return; + + // Remove all references + ListRemove(m_ThreadList, pLuaThread); + ListRemove(m_ProcessQueue, pLuaThread); + + if (m_pProcessingThread == pLuaThread) + { + assert(!m_pPendingDelete); + pLuaThread->RemoveScriptID(); + m_pPendingDelete = pLuaThread; + } + else + delete pLuaThread; +} + +void CLuaThreadManager::RemoveAllThreads() +{ + // Delete all the Threads + CFastList::const_iterator iter = m_ThreadList.begin(); + for (; iter != m_ThreadList.end(); ++iter) + { + delete *iter; + } + + // Clear the Thread list + m_ThreadList.clear(); + m_ProcessQueue.clear(); + m_pPendingDelete = NULL; + m_pProcessingThread = NULL; +} + +CLuaThread* CLuaThreadManager::GetThreadFromScriptID(uint uiScriptID) +{ + CLuaThread* pLuaThread = (CLuaThread*)CIdArray::FindEntry(uiScriptID, EIdClass::THREAD); + if (!pLuaThread) + return NULL; + + if (!ListContains(m_ThreadList, pLuaThread)) + return NULL; + return pLuaThread; +} + +CLuaThread* CLuaThreadManager::AddThread(const std::string& code) +{ + CLuaThread* pLuaThread = new CLuaThread(code); + m_ThreadList.push_back(pLuaThread); + return pLuaThread; +} diff --git a/Server/mods/deathmatch/logic/lua/CLuaThreadManager.h b/Server/mods/deathmatch/logic/lua/CLuaThreadManager.h new file mode 100644 index 00000000000..8b65cfcee69 --- /dev/null +++ b/Server/mods/deathmatch/logic/lua/CLuaThreadManager.h @@ -0,0 +1,47 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/lua/CLuaThreadManager.h + * PURPOSE: Lua thread manager class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +class CLuaThreadManager; + +#pragma once + +#include "LuaCommon.h" +#include "CLuaThread.h" +#include + +class CLuaThreadManager +{ +public: + CLuaThreadManager() + { + m_pPendingDelete = NULL; + m_pProcessingThread = NULL; + } + ~CLuaThreadManager() { RemoveAllThreads(); }; + + void DoPulse(CLuaMain* pLuaMain); + + CLuaThread* GetThreadFromScriptID(unsigned int uiScriptID); + + CLuaThread* AddThread(const std::string&); + void RemoveThread(CLuaThread* pLuaTimer); + void RemoveAllThreads(); + unsigned long GetThreadCount() const { return m_ThreadList.size(); } + + CFastList::const_iterator IterBegin() { return m_ThreadList.begin(); } + CFastList::const_iterator IterEnd() { return m_ThreadList.end(); } + +private: + CFastList m_ThreadList; + std::deque m_ProcessQueue; + CLuaThread* m_pPendingDelete; + CLuaThread* m_pProcessingThread; +}; diff --git a/Server/mods/deathmatch/logic/lua/LuaCommon.cpp b/Server/mods/deathmatch/logic/lua/LuaCommon.cpp index 231378f582a..3c80537c2fc 100644 --- a/Server/mods/deathmatch/logic/lua/LuaCommon.cpp +++ b/Server/mods/deathmatch/logic/lua/LuaCommon.cpp @@ -132,6 +132,14 @@ void lua_pushtimer(lua_State* luaVM, CLuaTimer* pTimer) lua_pushobject(luaVM, szClass, (void*)reinterpret_cast(pTimer->GetScriptID())); } +void lua_pushluathread(lua_State* luaVM, CLuaThread* pThread) +{ + const char* szClass = NULL; + CLuaMain* pLuaMain = g_pGame->GetLuaManager()->GetVirtualMachine(luaVM); + + lua_pushobject(luaVM, szClass, (void*)reinterpret_cast(pThread->GetScriptID())); +} + void lua_pushxmlnode(lua_State* luaVM, CXMLNode* pElement) { const char* szClass = NULL; @@ -174,6 +182,8 @@ void lua_pushuserdata(lua_State* luaVM, void* pData) return lua_pushxmlnode(luaVM, pNode); else if (CLuaTimer* pTimer = UserDataCast((CLuaTimer*)NULL, pData, luaVM)) return lua_pushtimer(luaVM, pTimer); + else if (CLuaThread* pThread = UserDataCast((CLuaThread*)NULL, pData, luaVM)) + return lua_pushluathread(luaVM, pThread); else if (CLuaVector2D* pVector = UserDataCast((CLuaVector2D*)NULL, pData, luaVM)) return lua_pushvector(luaVM, *pVector); else if (CLuaVector3D* pVector = UserDataCast((CLuaVector3D*)NULL, pData, luaVM)) diff --git a/Server/mods/deathmatch/logic/lua/LuaCommon.h b/Server/mods/deathmatch/logic/lua/LuaCommon.h index 4e33e79adeb..74306f28ab9 100644 --- a/Server/mods/deathmatch/logic/lua/LuaCommon.h +++ b/Server/mods/deathmatch/logic/lua/LuaCommon.h @@ -34,6 +34,7 @@ void lua_pushresource(lua_State* luaVM, class CResource* pResource); void lua_pushtextdisplay(lua_State* luaVM, class CTextDisplay* pDisplay); void lua_pushtextitem(lua_State* luaVM, class CTextItem* pItem); void lua_pushtimer(lua_State* luaVM, class CLuaTimer* pTimer); +void lua_pushluathread(lua_State* luaVM, class CLuaThread* pThread); void lua_pushxmlnode(lua_State* luaVM, class CXMLNode* pNode); void lua_pushban(lua_State* luaVM, class CBan* pBan); void lua_pushquery(lua_State* luaVM, class CDbJobData* pJobData); diff --git a/Shared/mods/deathmatch/logic/CIdArray.h b/Shared/mods/deathmatch/logic/CIdArray.h index 1bff57cf5f5..76575d490c4 100644 --- a/Shared/mods/deathmatch/logic/CIdArray.h +++ b/Shared/mods/deathmatch/logic/CIdArray.h @@ -52,7 +52,8 @@ namespace EIdClass VECTOR2, VECTOR3, VECTOR4, - MATRIX + MATRIX, + THREAD }; }; diff --git a/Shared/mods/deathmatch/logic/Enums.cpp b/Shared/mods/deathmatch/logic/Enums.cpp index 00a7645af96..872b18fa42a 100644 --- a/Shared/mods/deathmatch/logic/Enums.cpp +++ b/Shared/mods/deathmatch/logic/Enums.cpp @@ -56,6 +56,14 @@ IMPLEMENT_ENUM_CLASS_BEGIN(StringEncryptFunction) ADD_ENUM(StringEncryptFunction::TEA, "tea") IMPLEMENT_ENUM_CLASS_END("string-encrypt-function") +IMPLEMENT_ENUM_CLASS_BEGIN(EThreadState) +ADD_ENUM(EThreadState::BUSY, "busy") +ADD_ENUM(EThreadState::FAILURE, "failure") +ADD_ENUM(EThreadState::IDLE, "idle") +ADD_ENUM(EThreadState::INITIALIZING, "initializing") +ADD_ENUM(EThreadState::EXIT, "exit") +IMPLEMENT_ENUM_CLASS_END("thread-state") + IMPLEMENT_ENUM_BEGIN(ePacketID) ADD_ENUM1(PACKET_ID_SERVER_JOIN) ADD_ENUM1(PACKET_ID_SERVER_JOIN_DATA) diff --git a/Shared/mods/deathmatch/logic/Enums.h b/Shared/mods/deathmatch/logic/Enums.h index af9e4739577..a93109a8ade 100644 --- a/Shared/mods/deathmatch/logic/Enums.h +++ b/Shared/mods/deathmatch/logic/Enums.h @@ -67,4 +67,21 @@ DECLARE_ENUM(EHashFunction::EHashFunctionType); DECLARE_ENUM_CLASS(PasswordHashFunction); DECLARE_ENUM_CLASS(StringEncryptFunction); + +enum class EThreadState +{ + // lua virtual machine is being creation + INITIALIZING, + // something went wrong while initializing + FAILURE, + // thread is executing code + BUSY, + // does nothing, sleeping + IDLE, + // thread exited + EXIT, +}; + +DECLARE_ENUM_CLASS(EThreadState); + DECLARE_ENUM(ePacketID); diff --git a/Shared/mods/deathmatch/logic/lua/CLuaThread.cpp b/Shared/mods/deathmatch/logic/lua/CLuaThread.cpp new file mode 100644 index 00000000000..019445fbebf --- /dev/null +++ b/Shared/mods/deathmatch/logic/lua/CLuaThread.cpp @@ -0,0 +1,173 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/lua/CLuaThread.cpp + * PURPOSE: Lua thread class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" + +CLuaThread::CLuaThread(const std::string& code) +{ + m_uiScriptID = CIdArray::PopUniqueId(this, EIdClass::THREAD); + m_strCode = code; + m_pAsyncTaskSheduler = std::make_unique(1); + SetState(EThreadState::INITIALIZING); + m_pAsyncTaskSheduler->PushTask( + [&] { + m_luaVM = lua_open(); + + luaopen_base(m_luaVM); + luaopen_math(m_luaVM); + luaopen_string(m_luaVM); + luaopen_table(m_luaVM); + luaopen_debug(m_luaVM); + luaopen_utf8(m_luaVM); + luaopen_os(m_luaVM); + + CLuaMain::InitSecurity(m_luaVM); + + // Registering C functions + CLuaCFunctions::RegisterThreadFunctionsWithVM(m_luaVM); + + int stackSize = lua_gettop(m_luaVM); + lua_pop(m_luaVM, stackSize); + + LoadUserCode(); + + return true; + }, + [&](bool success) { + if (success) + { + } + }); +} + +CLuaThread::~CLuaThread() +{ + Close(); + RemoveScriptID(); +} + +void CLuaThread::SetState(EThreadState state) +{ + std::lock_guard guard(m_lock); + m_eState = state; +} + +void CLuaThread::Close() +{ + if (m_luaVM) + { + lua_close(m_luaVM); + m_luaVM = nullptr; + } +} +void CLuaThread::LoadUserCode() +{ + int iResult = luaL_loadbuffer(m_luaVM, reinterpret_cast(m_strCode.c_str()), m_strCode.length(), "foo"); + if (iResult == LUA_ERRRUN || iResult == LUA_ERRMEM || iResult == LUA_ERRSYNTAX) + { + const char* result = lua_tostring(m_luaVM, -1); + m_strError = std::string(result); + SetState(EThreadState::FAILURE); + return; + } + + SetState(EThreadState::BUSY); + + int iret = lua_pcall(m_luaVM, 0, LUA_MULTRET, 0); + + CScriptArgReader argStream(m_luaVM); + { + std::lock_guard guard(m_lockReturnArguments); + argStream.ReadLuaArguments(m_returnArguments); + m_bHasReturnArguments = true; + } + + Idle(); + return; +} + +void CLuaThread::LoadScript(const char* code) +{ + // Run the script + if (!CLuaMain::LuaLoadBuffer(m_luaVM, code, strlen(code), NULL)) + { + int luaSavedTop = lua_gettop(m_luaVM); + int iret = lua_pcall(m_luaVM, 0, LUA_MULTRET, 0); + if (iret == LUA_ERRRUN || iret == LUA_ERRMEM) + { + std::string strRes = ConformResourcePath(lua_tostring(m_luaVM, -1)); + //g_pGame->GetScriptDebugging()->LogPCallError(m_luaVM, strRes); + } + // Cleanup any return values + if (lua_gettop(m_luaVM) > luaSavedTop) + lua_settop(m_luaVM, luaSavedTop); + } + else + { + std::string strRes = ConformResourcePath(lua_tostring(m_luaVM, -1)); + //g_pGame->GetScriptDebugging()->LogError(m_luaVM, "Loading in-line script failed: %s", strRes.c_str()); + } +} + +void CLuaThread::Idle() +{ + SetState(EThreadState::IDLE); +} + +void CLuaThread::DoPulse() +{ + m_pAsyncTaskSheduler->CollectResults(); +} + +void CLuaThread::Call(const std::string& functionName, const CLuaArguments& arguments, CLuaArguments& returns) +{ + SetState(EThreadState::BUSY); + arguments.CallGlobal(m_luaVM, functionName.c_str(), &returns); + Idle(); +} + +void CLuaThread::Call(const std::string& functionName, const CLuaArguments& arguments) +{ + m_pAsyncTaskSheduler->PushTask( + [&, arguments] { + CLuaArguments returns; + SetState(EThreadState::BUSY); + arguments.CallGlobal(m_luaVM, functionName.c_str(), &returns); + return true; + }, + [&](bool __) { Idle(); }); +} + +EThreadState CLuaThread::GetState() +{ + std::lock_guard guard(m_lock); + return m_eState; +} + +bool CLuaThread::GetReturnArguments(CLuaArguments& arguments) +{ + std::lock_guard guard(m_lockReturnArguments); + if (m_bHasReturnArguments) + { + arguments = m_returnArguments; + return true; + } + return false; +} + +void CLuaThread::RemoveScriptID() +{ + if (m_uiScriptID != INVALID_ARRAY_ID) + { + CIdArray::PushUniqueId(this, EIdClass::THREAD, m_uiScriptID); + m_uiScriptID = INVALID_ARRAY_ID; + } +} diff --git a/Shared/mods/deathmatch/logic/lua/CLuaThread.h b/Shared/mods/deathmatch/logic/lua/CLuaThread.h new file mode 100644 index 00000000000..f599cf9c750 --- /dev/null +++ b/Shared/mods/deathmatch/logic/lua/CLuaThread.h @@ -0,0 +1,54 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/lua/CLuaThread.h + * PURPOSE: Lua thread class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +class CLuaThread; + +#pragma once + +class CLuaThread +{ +public: + CLuaThread(const std::string& code); + ~CLuaThread(); + + void RemoveScriptID(); + void DoPulse(); + void Idle(); + + uint GetScriptID() const { return m_uiScriptID; } + EThreadState GetState(); + void Call(const std::string& functionName, const CLuaArguments& arguments, CLuaArguments& returns); + void Call(const std::string& functionName, const CLuaArguments& arguments); + + bool GetReturnArguments(CLuaArguments& arguments); + +private: + void Close(); + + void SetState(EThreadState state); + void LoadScript(const char* code); + void LoadUserCode(); + + std::string m_strCode; + + std::mutex m_lockReturnArguments; + bool m_bHasReturnArguments = false; + CLuaArguments m_returnArguments; + + std::unique_ptr m_pAsyncTaskSheduler; + uint m_uiScriptID; + + EThreadState m_eState; + std::mutex m_lock; + lua_State* m_luaVM; + std::string m_strError; + bool m_bIsBusy; +}; diff --git a/Shared/mods/deathmatch/logic/lua/CLuaThreadManager.cpp b/Shared/mods/deathmatch/logic/lua/CLuaThreadManager.cpp new file mode 100644 index 00000000000..21e1e532a84 --- /dev/null +++ b/Shared/mods/deathmatch/logic/lua/CLuaThreadManager.cpp @@ -0,0 +1,84 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/lua/CLuaThreadManager.cpp + * PURPOSE: Lua thread manager class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" + +void CLuaThreadManager::DoPulse(CLuaMain* pLuaMain) +{ + assert(m_ProcessQueue.empty()); + assert(!m_pPendingDelete); + assert(!m_pProcessingThread); + + CTickCount llCurrentTime = CTickCount::Now(); + + // Delete all the Threads + CFastList::const_iterator iter = m_ThreadList.begin(); + for (; iter != m_ThreadList.end(); ++iter) + { + (*iter)->DoPulse(); + } +} + +void CLuaThreadManager::RemoveThread(CLuaThread* pLuaThread) +{ + assert(pLuaThread); + + // Check if already removed + if (!ListContains(m_ThreadList, pLuaThread)) + return; + + // Remove all references + ListRemove(m_ThreadList, pLuaThread); + ListRemove(m_ProcessQueue, pLuaThread); + + if (m_pProcessingThread == pLuaThread) + { + assert(!m_pPendingDelete); + pLuaThread->RemoveScriptID(); + m_pPendingDelete = pLuaThread; + } + else + delete pLuaThread; +} + +void CLuaThreadManager::RemoveAllThreads() +{ + // Delete all the Threads + CFastList::const_iterator iter = m_ThreadList.begin(); + for (; iter != m_ThreadList.end(); ++iter) + { + delete *iter; + } + + // Clear the Thread list + m_ThreadList.clear(); + m_ProcessQueue.clear(); + m_pPendingDelete = NULL; + m_pProcessingThread = NULL; +} + +CLuaThread* CLuaThreadManager::GetThreadFromScriptID(uint uiScriptID) +{ + CLuaThread* pLuaThread = (CLuaThread*)CIdArray::FindEntry(uiScriptID, EIdClass::THREAD); + if (!pLuaThread) + return NULL; + + if (!ListContains(m_ThreadList, pLuaThread)) + return NULL; + return pLuaThread; +} + +CLuaThread* CLuaThreadManager::AddThread(const std::string& code) +{ + CLuaThread* pLuaThread = new CLuaThread(code); + m_ThreadList.push_back(pLuaThread); + return pLuaThread; +} diff --git a/Shared/mods/deathmatch/logic/lua/CLuaThreadManager.h b/Shared/mods/deathmatch/logic/lua/CLuaThreadManager.h new file mode 100644 index 00000000000..de55141cf6d --- /dev/null +++ b/Shared/mods/deathmatch/logic/lua/CLuaThreadManager.h @@ -0,0 +1,46 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/lua/CLuaThreadManager.h + * PURPOSE: Lua thread manager class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +class CLuaThreadManager; + +#pragma once + +#include "CLuaThread.h" +#include + +class CLuaThreadManager +{ +public: + CLuaThreadManager() + { + m_pPendingDelete = NULL; + m_pProcessingThread = NULL; + } + ~CLuaThreadManager() { RemoveAllThreads(); }; + + void DoPulse(CLuaMain* pLuaMain); + + CLuaThread* GetThreadFromScriptID(unsigned int uiScriptID); + + CLuaThread* AddThread(const std::string&); + void RemoveThread(CLuaThread* pLuaTimer); + void RemoveAllThreads(); + unsigned long GetThreadCount() const { return m_ThreadList.size(); } + + CFastList::const_iterator IterBegin() { return m_ThreadList.begin(); } + CFastList::const_iterator IterEnd() { return m_ThreadList.end(); } + +private: + CFastList m_ThreadList; + std::deque m_ProcessQueue; + CLuaThread* m_pPendingDelete; + CLuaThread* m_pProcessingThread; +}; diff --git a/Shared/mods/deathmatch/logic/lua/LuaBasic.h b/Shared/mods/deathmatch/logic/lua/LuaBasic.h index 6838e9b7271..e46e98c35e9 100644 --- a/Shared/mods/deathmatch/logic/lua/LuaBasic.h +++ b/Shared/mods/deathmatch/logic/lua/LuaBasic.h @@ -20,6 +20,7 @@ class CVector2D; class CVector; class CVector4D; +class CLuaThread; namespace lua { @@ -110,6 +111,12 @@ namespace lua lua_pushmatrix(L, value); return 1; } + + inline int Push(lua_State* L, CLuaThread* value) + { + lua_pushluathread(L, value); + return 1; + } // Overload for enum types only template diff --git a/Shared/mods/deathmatch/logic/luadefs/CLuaThreadDefs.cpp b/Shared/mods/deathmatch/logic/luadefs/CLuaThreadDefs.cpp new file mode 100644 index 00000000000..a9b18a02329 --- /dev/null +++ b/Shared/mods/deathmatch/logic/luadefs/CLuaThreadDefs.cpp @@ -0,0 +1,95 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/luadefs/CLuaThreadDefs.cpp + * PURPOSE: Lua function definitions class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" +#include "lua/CLuaFunctionParser.h" + +void CLuaThreadDefs::LoadFunctions() +{ + constexpr static const std::pair functions[]{ + {"createThread", ArgumentParser}, + {"getThreadState", ArgumentParser}, + {"threadCall", ArgumentParser}, + {"getThreadResult", GetThreadResult}, + }; + + // Add functions + for (const auto& [name, func] : functions) + CLuaCFunctions::AddFunction(name, func); +} + +void CLuaThreadDefs::AddClass(lua_State* luaVM) +{ +} + +CLuaThread* CLuaThreadDefs::CreateThread(lua_State* luaVM, std::string strCode) +{ + CLuaMain* luaMain = m_pLuaManager->GetVirtualMachine(luaVM); + if (luaMain) + { + CLuaThread* pLuaThread = luaMain->GetThreadManager()->AddThread(strCode); + if (pLuaThread) + { + return pLuaThread; + } + } + + throw new std::invalid_argument("Error"); +} + +EThreadState CLuaThreadDefs::GetThreadState(CLuaThread* pThread) +{ + return pThread->GetState(); +} + +bool CLuaThreadDefs::ThreadCall(CLuaThread* pThread, std::string strFunction, std::optional> optionalArguments) +{ + std::vector argumentList = optionalArguments.value_or(std::vector()); + CLuaArguments arguments; + for (auto const& argument : argumentList) + arguments.PushArgument(argument); + + CLuaShared::GetAsyncTaskScheduler()->PushTask( + [pThread, strFunction, arguments] { + pThread->Call(strFunction, arguments); + return true; + }, + [](bool __) { }); + return true; +} + +int CLuaThreadDefs::GetThreadResult(lua_State* luaVM) +{ + CLuaThread* pLuaThread; + + CScriptArgReader argStream(luaVM); + argStream.ReadUserData(pLuaThread); + + if (!argStream.HasErrors()) + { + CLuaArguments arguments; + if (pLuaThread->GetReturnArguments(arguments)) + { + arguments.PushArguments(luaVM); + return arguments.Count(); + } + else + { + lua_pushnil(luaVM); + return 1; + } + } + else + m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); + + lua_pushboolean(luaVM, false); + return 1; +} diff --git a/Shared/mods/deathmatch/logic/luadefs/CLuaThreadDefs.h b/Shared/mods/deathmatch/logic/luadefs/CLuaThreadDefs.h new file mode 100644 index 00000000000..6686fbcbbe9 --- /dev/null +++ b/Shared/mods/deathmatch/logic/luadefs/CLuaThreadDefs.h @@ -0,0 +1,24 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/luadefs/CLuaThreadDefs.h + * PURPOSE: Lua function definitions class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +class CLuaThreadDefs : public CLuaDefs +{ +public: + static void LoadFunctions(); + static void AddClass(lua_State* luaVM); + + static CLuaThread* CreateThread(lua_State* luaVM, std::string strCode); + static EThreadState GetThreadState(CLuaThread* pThread); + static bool ThreadCall(CLuaThread* pThread, std::string strFunction, std::optional> optionalArguments); + static int GetThreadResult(lua_State* luaVM); +};