// LuaState.cpp // Implements the cLuaState class representing the wrapper over lua_State *, provides associated helper functions #include "Globals.h" #include "LuaState.h" #include "lua/src/lualib.h" #undef TOLUA_TEMPLATE_BIND #include "tolua++/include/tolua++.h" #include "Bindings.h" #include "ManualBindings.h" #include "DeprecatedBindings.h" #include "LuaJson.h" #include "../Entities/Entity.h" #include "../BlockEntities/BlockEntity.h" #include "../DeadlockDetect.h" #include "../UUID.h" // Hotpatching the Macro to prevent a Clang Warning (0 for pointer used) #undef lua_tostring #define lua_tostring(L, i) lua_tolstring(L, (i), nullptr) // fwd: "SQLite/lsqlite3.cpp" int luaopen_lsqlite3(lua_State * L); // fwd: "LuaExpat/lxplib.cpp": int luaopen_lxp(lua_State * L); const cLuaState::cRet cLuaState::Return = {}; const cLuaState::cNil cLuaState::Nil = {}; //////////////////////////////////////////////////////////////////////////////// // cCanonLuaStates: /** Tracks the canon cLuaState instances for each lua_State pointer. Used for tracked refs - the ref needs to be tracked by a single cLuaState (the canon state), despite being created from a different (attached) cLuaState. The canon state must be available without accessing the Lua state itself (so it cannot be stored within Lua). */ class cCanonLuaStates { public: /** Returns the canon Lua state for the specified lua_State pointer. */ static cLuaState * GetCanonState(lua_State * a_LuaState) { auto & inst = GetInstance(); cCSLock lock(inst.m_CS); auto itr = inst.m_CanonStates.find(a_LuaState); if (itr == inst.m_CanonStates.end()) { return nullptr; } return itr->second; } /** Adds a new canon cLuaState instance to the map. Used when a new Lua state is created, this informs the map that a new canon Lua state should be tracked. */ static void Add(cLuaState & a_LuaState) { auto & inst = GetInstance(); cCSLock lock(inst.m_CS); ASSERT(inst.m_CanonStates.find(a_LuaState) == inst.m_CanonStates.end()); inst.m_CanonStates[a_LuaState.operator lua_State *()] = &a_LuaState; } /** Removes the bindings between the specified canon state and its lua_State pointer. Used when a Lua state is being closed. */ static void Remove(cLuaState & a_LuaState) { auto & inst = GetInstance(); cCSLock lock(inst.m_CS); auto itr = inst.m_CanonStates.find(a_LuaState); ASSERT(itr != inst.m_CanonStates.end()); inst.m_CanonStates.erase(itr); } protected: /** The mutex protecting m_CanonStates against multithreaded access. */ cCriticalSection m_CS; /** Map of lua_State pointers to their canon cLuaState instances. */ std::map m_CanonStates; /** Returns the singleton instance of this class. */ static cCanonLuaStates & GetInstance(void) { static cCanonLuaStates canonLuaStates; return canonLuaStates; } }; //////////////////////////////////////////////////////////////////////////////// // cLuaStateTracker: void cLuaStateTracker::Add(cLuaState & a_LuaState) { auto & Instance = Get(); cCSLock Lock(Instance.m_CSLuaStates); Instance.m_LuaStates.push_back(&a_LuaState); } void cLuaStateTracker::Del(cLuaState & a_LuaState) { auto & Instance = Get(); cCSLock Lock(Instance.m_CSLuaStates); Instance.m_LuaStates.erase( std::remove_if( Instance.m_LuaStates.begin(), Instance.m_LuaStates.end(), [&a_LuaState](cLuaStatePtr a_StoredLuaState) { return (&a_LuaState == a_StoredLuaState); } ), Instance.m_LuaStates.end() ); } AString cLuaStateTracker::GetStats(void) { auto & Instance = Get(); cCSLock Lock(Instance.m_CSLuaStates); AString res; int Total = 0; for (auto state: Instance.m_LuaStates) { int Mem = 0; if (!state->Call("collectgarbage", "count", cLuaState::Return, Mem)) { res.append(Printf("Cannot query memory for state \"%s\"\n", state->GetSubsystemName().c_str())); } else { res.append(Printf("State \"%s\" is using %d KiB of memory\n", state->GetSubsystemName().c_str(), Mem)); Total += Mem; } } res.append(Printf("Total memory used by Lua: %d KiB\n", Total)); return res; } cLuaStateTracker & cLuaStateTracker::Get(void) { static cLuaStateTracker Inst; // The singleton return Inst; } //////////////////////////////////////////////////////////////////////////////// // cLuaState::cTrackedRef: cLuaState::cTrackedRef::cTrackedRef(void): m_CS(nullptr) { } bool cLuaState::cTrackedRef::RefStack(cLuaState & a_LuaState, int a_StackPos) { // Clear any previous callback: Clear(); // Add self to LuaState's callback-tracking: auto canonState = a_LuaState.QueryCanonLuaState(); canonState->TrackRef(*this); // Store the new callback: m_CS = &(canonState->m_CS); m_Ref.RefStack(*canonState, a_StackPos); return true; } void cLuaState::cTrackedRef::Clear(void) { // Free the reference: lua_State * luaState = nullptr; { auto cs = m_CS.load(); if (cs != nullptr) { cCSLock Lock(*cs); if (!m_Ref.IsValid()) { return; } luaState = m_Ref.GetLuaState(); m_Ref.UnRef(); } } m_CS = nullptr; // Remove from LuaState's callback-tracking: if (luaState == nullptr) { return; } cLuaState(luaState).UntrackRef(*this); } bool cLuaState::cTrackedRef::IsValid(void) { auto cs = m_CS.load(); if (cs == nullptr) { return false; } cCSLock lock(*cs); return m_Ref.IsValid(); } bool cLuaState::cTrackedRef::IsSameLuaState(cLuaState & a_LuaState) { auto cs = m_CS.load(); if (cs == nullptr) { return false; } cCSLock lock(*cs); if (!m_Ref.IsValid()) { return false; } auto canonState = a_LuaState.QueryCanonLuaState(); if (canonState == nullptr) { return false; } return (m_Ref.GetLuaState() == static_cast(*canonState)); } void cLuaState::cTrackedRef::Invalidate(void) { auto cs = m_CS.load(); if (cs == nullptr) { // Already invalid return; } cCSLock Lock(*cs); if (!m_Ref.IsValid()) { LOGD("%s: Inconsistent callback at %p, has a CS but an invalid Ref. This should not happen", __FUNCTION__, static_cast(this) ); return; } m_Ref.UnRef(); m_CS = nullptr; } //////////////////////////////////////////////////////////////////////////////// // cLuaState::cCallback: bool cLuaState::cCallback::RefStack(cLuaState & a_LuaState, int a_StackPos) { // Check if the stack contains a function: if (!lua_isfunction(a_LuaState, a_StackPos)) { return false; } return Super::RefStack(a_LuaState, a_StackPos); } //////////////////////////////////////////////////////////////////////////////// // cLuaState::cOptionalCallback: bool cLuaState::cOptionalCallback::RefStack(cLuaState & a_LuaState, int a_StackPos) { // If the stack pos is nil, make this an empty callback: if (lua_isnil(a_LuaState, a_StackPos)) { Clear(); return true; } // Use default cCallback implementation: return Super::RefStack(a_LuaState, a_StackPos); } //////////////////////////////////////////////////////////////////////////////// // cLuaState::cTableRef: bool cLuaState::cTableRef::RefStack(cLuaState & a_LuaState, int a_StackPos) { // Check if the stack contains a table: if (!lua_istable(a_LuaState, a_StackPos)) { return false; } return Super::RefStack(a_LuaState, a_StackPos); } //////////////////////////////////////////////////////////////////////////////// // cLuaState::cStackTable: cLuaState::cStackTable::cStackTable(cLuaState & a_LuaState, int a_StackPos): m_LuaState(a_LuaState), m_StackPos(a_StackPos) { ASSERT(lua_istable(a_LuaState, a_StackPos)); } void cLuaState::cStackTable::ForEachArrayElement(cFunctionRef a_ElementCallback) const { const auto numElements = luaL_getn(m_LuaState, m_StackPos); [[maybe_unused]] const auto stackTop = lua_gettop(m_LuaState); for (int idx = 1; idx <= numElements; idx++) { // Push the idx-th element of the array onto stack top and call the callback: lua_rawgeti(m_LuaState, m_StackPos, idx); auto shouldAbort = a_ElementCallback(m_LuaState, idx); ASSERT(lua_gettop(m_LuaState) == stackTop + 1); // The element callback must not change the Lua stack below the value lua_pop(m_LuaState, 1); if (shouldAbort) { // The callback wants to abort return; } } } void cLuaState::cStackTable::ForEachElement(cFunctionRef a_ElementCallback) const { [[maybe_unused]] const auto stackTop = lua_gettop(m_LuaState); lua_pushvalue(m_LuaState, m_StackPos); // Stk: lua_pushnil(m_LuaState); // Stk:
nil while (lua_next(m_LuaState, -2)) // Stk:
{ auto shouldAbort = a_ElementCallback(m_LuaState); ASSERT(lua_gettop(m_LuaState) == stackTop + 3); // The element callback must not change the Lua stack below the value lua_pop(m_LuaState, 1); // Stk:
if (shouldAbort) { // The callback wants to abort lua_pop(m_LuaState, 2); // Stk: empty return; } } // Stk:
lua_pop(m_LuaState, 1); // Stk: empty } //////////////////////////////////////////////////////////////////////////////// // cLuaState: cLuaState::cLuaState(const AString & a_SubsystemName) : m_LuaState(nullptr), m_IsOwned(false), m_SubsystemName(a_SubsystemName), m_NumCurrentFunctionArgs(-1) { } cLuaState::cLuaState(lua_State * a_AttachState) : m_LuaState(a_AttachState), m_IsOwned(false), m_SubsystemName(""), m_NumCurrentFunctionArgs(-1) { } cLuaState::~cLuaState() { if (IsValid()) { if (m_IsOwned) { Close(); } else { Detach(); } } } void cLuaState::Create(void) { if (m_LuaState != nullptr) { LOGWARNING("%s: Trying to create an already-existing LuaState, ignoring.", __FUNCTION__); return; } m_LuaState = lua_open(); luaL_openlibs(m_LuaState); m_IsOwned = true; cLuaStateTracker::Add(*this); cCanonLuaStates::Add(*this); } void cLuaState::RegisterAPILibs(void) { auto top = lua_gettop(m_LuaState); tolua_AllToLua_open(m_LuaState); ASSERT(top == lua_gettop(m_LuaState)); cManualBindings::Bind(m_LuaState); ASSERT(top == lua_gettop(m_LuaState)); DeprecatedBindings::Bind(m_LuaState); ASSERT(top == lua_gettop(m_LuaState)); cLuaJson::Bind(*this); ASSERT(top == lua_gettop(m_LuaState)); luaopen_lsqlite3(m_LuaState); if (top == lua_gettop(m_LuaState) - 1) { // lsqlite3 left the "sqlite3" table on the stack, pop it: lua_pop(m_LuaState, 1); } ASSERT(top == lua_gettop(m_LuaState)); luaopen_lxp(m_LuaState); if (top == lua_gettop(m_LuaState) - 1) { // lxp left the unregistered "lxp" table on the stack, register and pop it (#3304): lua_setglobal(m_LuaState, "lxp"); } ASSERT(top == lua_gettop(m_LuaState)); } void cLuaState::Close(void) { if (m_LuaState == nullptr) { LOGWARNING("%s: Trying to close an invalid LuaState, ignoring.", __FUNCTION__); return; } if (!m_IsOwned) { LOGWARNING( "%s: Detected mis-use, calling Close() on an attached state (0x%p). Detaching instead.", __FUNCTION__, static_cast(m_LuaState) ); Detach(); return; } // Invalidate all tracked refs: { cCSLock Lock(m_CSTrackedRefs); for (auto & r: m_TrackedRefs) { r->Invalidate(); } m_TrackedRefs.clear(); } cCanonLuaStates::Remove(*this); cLuaStateTracker::Del(*this); lua_close(m_LuaState); m_LuaState = nullptr; m_IsOwned = false; } void cLuaState::Attach(lua_State * a_State) { if (m_LuaState != nullptr) { LOGINFO("%s: Already contains a LuaState (0x%p), will be closed / detached.", __FUNCTION__, static_cast(m_LuaState)); if (m_IsOwned) { Close(); } else { Detach(); } } m_LuaState = a_State; m_IsOwned = false; } void cLuaState::Detach(void) { if (m_LuaState == nullptr) { return; } if (m_IsOwned) { LOGWARNING( "%s: Detected a mis-use, calling Detach() when the state is owned. Closing the owned state (0x%p).", __FUNCTION__, static_cast(m_LuaState) ); Close(); return; } m_LuaState = nullptr; } void cLuaState::AddPackagePath(const AString & a_PathVariable, const AString & a_Path) { ASSERT_LUA_STACK_BALANCE(m_LuaState); // Get the current path: lua_getfield(m_LuaState, LUA_GLOBALSINDEX, "package"); // Stk: lua_getfield(m_LuaState, -1, a_PathVariable.c_str()); // Stk: size_t len = 0; const char * PackagePath = lua_tolstring(m_LuaState, -1, &len); // Append the new path: AString NewPackagePath(PackagePath, len); NewPackagePath.append(LUA_PATHSEP); NewPackagePath.append(a_Path); // Set the new path to the environment: lua_pop(m_LuaState, 1); // Stk: lua_pushlstring(m_LuaState, NewPackagePath.c_str(), NewPackagePath.length()); // Stk: lua_setfield(m_LuaState, -2, a_PathVariable.c_str()); // Stk: lua_pop(m_LuaState, 1); // Stk: - } bool cLuaState::LoadFile(const AString & a_FileName, bool a_LogWarnings) { ASSERT(IsValid()); ASSERT_LUA_STACK_BALANCE(m_LuaState); // Load the file: int s = luaL_loadfile(m_LuaState, a_FileName.c_str()); if (s != 0) { if (a_LogWarnings) { LOGWARNING("Can't load %s because of a load error in file %s: %d (%s)", m_SubsystemName.c_str(), a_FileName.c_str(), s, lua_tostring(m_LuaState, -1)); } lua_pop(m_LuaState, 1); return false; } // Execute the globals: s = lua_pcall(m_LuaState, 0, 0, 0); if (s != 0) { if (a_LogWarnings) { LOGWARNING("Can't load %s because of an initialization error in file %s: %d (%s)", m_SubsystemName.c_str(), a_FileName.c_str(), s, lua_tostring(m_LuaState, -1)); } lua_pop(m_LuaState, 1); return false; } return true; } bool cLuaState::LoadString(const AString & a_StringToLoad, const AString & a_FileName, bool a_LogWarnings) { ASSERT(IsValid()); ASSERT_LUA_STACK_BALANCE(m_LuaState); // Load the file: int s = luaL_loadstring(m_LuaState, a_StringToLoad.c_str()); if (s != 0) { if (a_LogWarnings) { LOGWARNING("Can't load %s because of a load error in string from \"%s\": %d (%s)", m_SubsystemName.c_str(), a_FileName.c_str(), s, lua_tostring(m_LuaState, -1)); } lua_pop(m_LuaState, 1); return false; } // Execute the globals: s = lua_pcall(m_LuaState, 0, LUA_MULTRET, 0); if (s != 0) { if (a_LogWarnings) { LOGWARNING("Can't load %s because of an initialization error in string from \"%s\": %d (%s)", m_SubsystemName.c_str(), a_FileName.c_str(), s, lua_tostring(m_LuaState, -1)); } lua_pop(m_LuaState, 1); return false; } return true; } bool cLuaState::HasFunction(const char * a_FunctionName) { if (!IsValid()) { // This happens if cPlugin::Initialize() fails with an error return false; } ASSERT_LUA_STACK_BALANCE(m_LuaState); lua_getglobal(m_LuaState, a_FunctionName); bool res = (!lua_isnil(m_LuaState, -1) && lua_isfunction(m_LuaState, -1)); lua_pop(m_LuaState, 1); return res; } bool cLuaState::PushFunction(const char * a_FunctionName) { ASSERT(m_NumCurrentFunctionArgs == -1); // If not, there's already something pushed onto the stack if (!IsValid()) { // This happens if cPlugin::Initialize() fails with an error return false; } // Push the error handler for lua_pcall() lua_pushcfunction(m_LuaState, &ReportFnCallErrors); lua_getglobal(m_LuaState, a_FunctionName); if (!lua_isfunction(m_LuaState, -1)) { LOGWARNING("Error in %s: Could not find function %s()", m_SubsystemName.c_str(), a_FunctionName); lua_pop(m_LuaState, 2); return false; } m_CurrentFunctionName.assign(a_FunctionName); m_NumCurrentFunctionArgs = 0; return true; } bool cLuaState::PushFunction(const cRef & a_FnRef) { ASSERT(IsValid()); ASSERT(m_NumCurrentFunctionArgs == -1); // If not, there's already something pushed onto the stack // Push the error handler for lua_pcall() lua_pushcfunction(m_LuaState, &ReportFnCallErrors); lua_rawgeti(m_LuaState, LUA_REGISTRYINDEX, static_cast(a_FnRef)); // same as lua_getref() if (!lua_isfunction(m_LuaState, -1)) { lua_pop(m_LuaState, 2); return false; } m_CurrentFunctionName = ""; m_NumCurrentFunctionArgs = 0; return true; } bool cLuaState::PushFunction(const cRef & a_TableRef, const char * a_FnName) { ASSERT(IsValid()); ASSERT(m_NumCurrentFunctionArgs == -1); // If not, there's already something pushed onto the stack if (!a_TableRef.IsValid()) { return false; } // Push the error handler for lua_pcall() lua_pushcfunction(m_LuaState, &ReportFnCallErrors); // Get the function from the table: lua_rawgeti(m_LuaState, LUA_REGISTRYINDEX, static_cast(a_TableRef)); if (!lua_istable(m_LuaState, -1)) { // Not a table, bail out lua_pop(m_LuaState, 2); return false; } lua_getfield(m_LuaState, -1, a_FnName); if (lua_isnil(m_LuaState, -1) || !lua_isfunction(m_LuaState, -1)) { // Not a valid function, bail out lua_pop(m_LuaState, 3); return false; } // Pop the table off the stack: lua_remove(m_LuaState, -2); Printf(m_CurrentFunctionName, "", a_FnName); m_NumCurrentFunctionArgs = 0; return true; } void cLuaState::Push(const AString & a_String) { ASSERT(IsValid()); lua_pushlstring(m_LuaState, a_String.data(), a_String.size()); } void cLuaState::Push(const AStringMap & a_Dictionary) { ASSERT(IsValid()); lua_createtable(m_LuaState, 0, static_cast(a_Dictionary.size())); int newTable = lua_gettop(m_LuaState); for (const auto & item: a_Dictionary) { Push(item.first); // key Push(item.second); // value lua_rawset(m_LuaState, newTable); } } void cLuaState::Push(const AStringVector & a_Vector) { ASSERT(IsValid()); lua_createtable(m_LuaState, static_cast(a_Vector.size()), 0); int newTable = lua_gettop(m_LuaState); int index = 1; for (AStringVector::const_iterator itr = a_Vector.begin(), end = a_Vector.end(); itr != end; ++itr, ++index) { Push(*itr); lua_rawseti(m_LuaState, newTable, index); } } void cLuaState::Push(const char * a_Value) { ASSERT(IsValid()); tolua_pushstring(m_LuaState, a_Value); } void cLuaState::Push(const cItem & a_Item) { ASSERT(IsValid()); auto c = new cItem(a_Item); tolua_pushusertype_and_takeownership(m_LuaState, c, "cItem"); } void cLuaState::Push(const cNil & a_Nil) { ASSERT(IsValid()); lua_pushnil(m_LuaState); } void cLuaState::Push(const cLuaState::cRef & a_Ref) { ASSERT(IsValid()); lua_rawgeti(m_LuaState, LUA_REGISTRYINDEX, static_cast(a_Ref)); } void cLuaState::Push(const ContiguousByteBufferView a_Data) { ASSERT(IsValid()); lua_pushlstring(m_LuaState, reinterpret_cast(a_Data.data()), a_Data.size()); } void cLuaState::Push(const Vector3d & a_Vector) { ASSERT(IsValid()); auto c = new Vector3d(a_Vector); tolua_pushusertype_and_takeownership(m_LuaState, c, "Vector3"); } void cLuaState::Push(const Vector3i & a_Vector) { ASSERT(IsValid()); auto c = new Vector3i(a_Vector); tolua_pushusertype_and_takeownership(m_LuaState, c, "Vector3"); } void cLuaState::Push(bool a_Value) { ASSERT(IsValid()); tolua_pushboolean(m_LuaState, a_Value ? 1 : 0); } void cLuaState::Push(const cEntity * a_Entity) { // Once we can make Lua understand constness, this function shall receive a corresponding function body Push(const_cast(a_Entity)); } void cLuaState::Push(cEntity * a_Entity) { ASSERT(IsValid()); if (a_Entity == nullptr) { lua_pushnil(m_LuaState); } else { const char * ClassName = [&] { switch (a_Entity->GetEntityType()) { case cEntity::etBoat: return "cBoat"; case cEntity::etExpOrb: return "cExpOrb"; case cEntity::etFallingBlock: return "cFallingBlock"; case cEntity::etFloater: return "cFloater"; case cEntity::etItemFrame: return "cItemFrame"; case cEntity::etLeashKnot: return "cLeashKnot"; case cEntity::etPainting: return "cPainting"; case cEntity::etPickup: return "cPickup"; case cEntity::etPlayer: return "cPlayer"; case cEntity::etTNT: return "cTNTEntity"; case cEntity::etMonster: { // Don't push specific mob types, as those are not exported in the API: return "cMonster"; } case cEntity::etProjectile: { // Push the specific projectile type: return a_Entity->GetClass(); } case cEntity::etEntity: case cEntity::etEnderCrystal: case cEntity::etMinecart: { // Push the generic entity class type: return "cEntity"; } } // switch (EntityType) UNREACHABLE("Unsupported entity type"); }(); tolua_pushusertype(m_LuaState, a_Entity, ClassName); } } void cLuaState::Push(cLuaServerHandle * a_ServerHandle) { ASSERT(IsValid()); tolua_pushusertype(m_LuaState, a_ServerHandle, "cServerHandle"); } void cLuaState::Push(cLuaTCPLink * a_TCPLink) { ASSERT(IsValid()); tolua_pushusertype(m_LuaState, a_TCPLink, "cTCPLink"); } void cLuaState::Push(cLuaUDPEndpoint * a_UDPEndpoint) { ASSERT(IsValid()); tolua_pushusertype(m_LuaState, a_UDPEndpoint, "cUDPEndpoint"); } void cLuaState::Push(double a_Value) { ASSERT(IsValid()); tolua_pushnumber(m_LuaState, a_Value); } void cLuaState::Push(int a_Value) { ASSERT(IsValid()); tolua_pushnumber(m_LuaState, a_Value); } void cLuaState::Push(long a_Value) { ASSERT(IsValid()); tolua_pushnumber(m_LuaState, static_cast(a_Value)); } void cLuaState::Push(UInt32 a_Value) { ASSERT(IsValid()); tolua_pushnumber(m_LuaState, a_Value); } void cLuaState::Push(std::chrono::milliseconds a_Value) { ASSERT(IsValid()); tolua_pushnumber(m_LuaState, static_cast(a_Value.count())); } void cLuaState::Pop(int a_NumValuesToPop) { ASSERT(IsValid()); lua_pop(m_LuaState, a_NumValuesToPop); } bool cLuaState::GetStackValue(int a_StackPos, AString & a_Value) { size_t len = 0; const char * data = lua_tolstring(m_LuaState, a_StackPos, &len); if (data != nullptr) { a_Value.assign(data, len); return true; } return false; } bool cLuaState::GetStackValue(int a_StackPos, AStringMap & a_Value) { // Retrieve all values in a string => string dictionary table: if (!lua_istable(m_LuaState, a_StackPos)) { return false; } cStackTable tbl(*this, a_StackPos); bool isValid = true; tbl.ForEachElement([&isValid, &a_Value](cLuaState & a_LuaState) { AString key, val; if (a_LuaState.GetStackValues(-2, key, val)) { a_Value[key] = val; } else { isValid = false; return true; } return false; } ); return isValid; } bool cLuaState::GetStackValue(int a_StackPos, AStringVector & a_Value) { // Retrieve all values in an array of string table: if (!lua_istable(m_LuaState, a_StackPos)) { return false; } cStackTable tbl(*this, a_StackPos); bool isValid = true; tbl.ForEachArrayElement([&](cLuaState & a_LuaState, int a_Index) { AString tempStr; if (a_LuaState.GetStackValue(-1, tempStr)) { a_Value.push_back(std::move(tempStr)); } else { isValid = false; return true; } return false; } ); return isValid; } bool cLuaState::GetStackValue(int a_StackPos, bool & a_ReturnedVal) { a_ReturnedVal = (tolua_toboolean(m_LuaState, a_StackPos, a_ReturnedVal ? 1 : 0) > 0); return true; } bool cLuaState::GetStackValue(int a_StackPos, cCallback & a_Callback) { return a_Callback.RefStack(*this, a_StackPos); } bool cLuaState::GetStackValue(int a_StackPos, cCallbackPtr & a_Callback) { if (a_Callback == nullptr) { a_Callback = std::make_unique(); } return a_Callback->RefStack(*this, a_StackPos); } bool cLuaState::GetStackValue(int a_StackPos, cOptionalCallback & a_Callback) { return a_Callback.RefStack(*this, a_StackPos); } bool cLuaState::GetStackValue(int a_StackPos, cOptionalCallbackPtr & a_Callback) { if (a_Callback == nullptr) { a_Callback = std::make_unique(); } return a_Callback->RefStack(*this, a_StackPos); } bool cLuaState::GetStackValue(int a_StackPos, cCallbackSharedPtr & a_Callback) { if (a_Callback == nullptr) { a_Callback = std::make_shared(); } return a_Callback->RefStack(*this, a_StackPos); } bool cLuaState::GetStackValue(int a_StackPos, cPluginManager::CommandResult & a_Result) { if (lua_isnumber(m_LuaState, a_StackPos)) { a_Result = static_cast(static_cast((tolua_tonumber(m_LuaState, a_StackPos, a_Result)))); return true; } return false; } bool cLuaState::GetStackValue(int a_StackPos, cRef & a_Ref) { a_Ref.RefStack(*this, a_StackPos); return true; } bool cLuaState::GetStackValue(int a_StackPos, cStackTablePtr & a_StackTable) { // Only allow tables to be stored in a_StackTable: if (!lua_istable(m_LuaState, a_StackPos)) { return false; } // Assign the StackTable to the specified stack position: a_StackTable = std::make_unique(*this, a_StackPos); return true; } bool cLuaState::GetStackValue(int a_StackPos, cTableRef & a_TableRef) { return a_TableRef.RefStack(*this, a_StackPos); } bool cLuaState::GetStackValue(int a_StackPos, cTableRefPtr & a_TableRef) { if (a_TableRef == nullptr) { a_TableRef = std::make_unique(); } return a_TableRef->RefStack(*this, a_StackPos); } bool cLuaState::GetStackValue(int a_StackPos, cTrackedRef & a_Ref) { return a_Ref.RefStack(*this, a_StackPos); } bool cLuaState::GetStackValue(int a_StackPos, cTrackedRefPtr & a_Ref) { if (a_Ref == nullptr) { a_Ref = std::make_unique(); } return a_Ref->RefStack(*this, a_StackPos); } bool cLuaState::GetStackValue(int a_StackPos, cTrackedRefSharedPtr & a_Ref) { if (a_Ref == nullptr) { a_Ref = std::make_shared(); } return a_Ref->RefStack(*this, a_StackPos); } bool cLuaState::GetStackValue(int a_StackPos, ContiguousByteBuffer & a_Data) { size_t Length = 0; const char * const Data = lua_tolstring(m_LuaState, a_StackPos, &Length); if (Data != nullptr) { a_Data.assign(reinterpret_cast(Data), Length); return true; } return false; } bool cLuaState::GetStackValue(int a_StackPos, CustomStatistic & a_Value) { if (lua_isnumber(m_LuaState, a_StackPos)) { a_Value = static_cast(static_cast>(lua_tonumber(m_LuaState, a_StackPos))); return true; } return true; } bool cLuaState::GetStackValue(int a_StackPos, double & a_ReturnedVal) { if (lua_isnumber(m_LuaState, a_StackPos)) { a_ReturnedVal = tolua_tonumber(m_LuaState, a_StackPos, a_ReturnedVal); return true; } return false; } bool cLuaState::GetStackValue(int a_StackPos, eBlockFace & a_ReturnedVal) { if (!lua_isnumber(m_LuaState, a_StackPos)) { return false; } a_ReturnedVal = static_cast(Clamp( static_cast(tolua_tonumber(m_LuaState, a_StackPos, a_ReturnedVal)), static_cast(BLOCK_FACE_MIN), static_cast(BLOCK_FACE_MAX)) ); return true; } bool cLuaState::GetStackValue(int a_StackPos, eWeather & a_ReturnedVal) { if (!lua_isnumber(m_LuaState, a_StackPos)) { return false; } a_ReturnedVal = static_cast(Clamp( static_cast(tolua_tonumber(m_LuaState, a_StackPos, a_ReturnedVal)), static_cast(wSunny), static_cast(wThunderstorm)) ); return true; } bool cLuaState::GetStackValue(int a_StackPos, float & a_ReturnedVal) { if (lua_isnumber(m_LuaState, a_StackPos)) { a_ReturnedVal = static_cast(tolua_tonumber(m_LuaState, a_StackPos, a_ReturnedVal)); return true; } return false; } bool cLuaState::GetStackValue(int a_StackPos, cUUID & a_Value) { if (lua_isnil(m_LuaState, a_StackPos)) { return false; } tolua_Error tolua_Err; if (tolua_isusertype(m_LuaState, a_StackPos, "cUUID", 0, &tolua_Err)) { // Found a cUUID, copy into output value cUUID * PtrUUID = nullptr; GetStackValue(a_StackPos, PtrUUID); if (PtrUUID == nullptr) { return false; } a_Value = *PtrUUID; return true; } // Try to get a string and parse as a UUID into the output AString StrUUID; if (!GetStackValue(a_StackPos, StrUUID)) { return false; } return a_Value.FromString(StrUUID); } bool cLuaState::GetStackValue(int a_StackPos, std::string_view & a_Value) { size_t Length = 0; const char * const Value = lua_tolstring(m_LuaState, a_StackPos, &Length); if (Value != nullptr) { a_Value = { Value, Length }; return true; } return false; } template bool cLuaState::GetStackValue(int a_StackPos, Vector3 & a_ReturnedVal) { tolua_Error err; if (lua_isnil(m_LuaState, a_StackPos)) { return false; } if (tolua_isusertype(m_LuaState, a_StackPos, "Vector3", 0, &err)) { a_ReturnedVal = **(static_cast(lua_touserdata(m_LuaState, a_StackPos))); return true; } if (tolua_isusertype(m_LuaState, a_StackPos, "Vector3", 0, &err)) { a_ReturnedVal = **(static_cast(lua_touserdata(m_LuaState, a_StackPos))); return true; } if (tolua_isusertype(m_LuaState, a_StackPos, "Vector3", 0, &err)) { a_ReturnedVal = **(static_cast(lua_touserdata(m_LuaState, a_StackPos))); return true; } // Bonus: Allow simple tables to work as Vector3: if (lua_istable(m_LuaState, a_StackPos)) { lua_rawgeti(m_LuaState, a_StackPos, 1); lua_rawgeti(m_LuaState, a_StackPos, 2); lua_rawgeti(m_LuaState, a_StackPos, 3); T x, y, z; if (!GetStackValues(-3, x, y, z)) { return false; } a_ReturnedVal = Vector3(x, y, z); return true; } return false; } // Explicitly instantiate the previous function for all Vector3 types: template bool cLuaState::GetStackValue(int a_StackPos, Vector3d & a_ReturnedVal); template bool cLuaState::GetStackValue(int a_StackPos, Vector3f & a_ReturnedVal); template bool cLuaState::GetStackValue(int a_StackPos, Vector3i & a_ReturnedVal); cLuaState::cStackValue cLuaState::WalkToValue(const AString & a_Name) { // There needs to be at least one value on the stack: ASSERT(lua_gettop(m_LuaState) > 0); // Iterate over path and replace the top of the stack with the walked element lua_pushvalue(m_LuaState, -1); // Copy the stack value into the "working area" auto path = StringSplit(a_Name, "."); for (const auto & elem: path) { // If the value is not a table, bail out (error): if (!lua_istable(m_LuaState, -1)) { lua_pop(m_LuaState, 1); return cStackValue(); } // Get the next part of the path: lua_getfield(m_LuaState, -1, elem.c_str()); // Remove the previous value from the stack (keep only the new one): lua_remove(m_LuaState, -2); } // for elem - path[] if (lua_isnil(m_LuaState, -1)) { lua_pop(m_LuaState, 1); return cStackValue(); } return cStackValue(*this); } cLuaState::cStackValue cLuaState::WalkToNamedGlobal(const AString & a_Name) { // Iterate over path and replace the top of the stack with the walked element lua_getglobal(m_LuaState, "_G"); auto path = StringSplit(a_Name, "."); for (const auto & elem: path) { // If the value is not a table, bail out (error): if (!lua_istable(m_LuaState, -1)) { lua_pop(m_LuaState, 1); return cStackValue(); } // Get the next part of the path: lua_getfield(m_LuaState, -1, elem.c_str()); // Remove the previous value from the stack (keep only the new one): lua_remove(m_LuaState, -2); } // for elem - path[] if (lua_isnil(m_LuaState, -1)) { lua_pop(m_LuaState, 1); return cStackValue(); } return cStackValue(*this); } bool cLuaState::CallFunction(int a_NumResults) { ASSERT (m_NumCurrentFunctionArgs >= 0); // A function must be pushed to stack first ASSERT(lua_isfunction(m_LuaState, -m_NumCurrentFunctionArgs - 1)); // The function to call ASSERT(lua_isfunction(m_LuaState, -m_NumCurrentFunctionArgs - 2)); // The error handler // Save the current "stack" state and reset, in case the callback calls another function: AString CurrentFunctionName; std::swap(m_CurrentFunctionName, CurrentFunctionName); int NumArgs = m_NumCurrentFunctionArgs; m_NumCurrentFunctionArgs = -1; // Call the function: int s = lua_pcall(m_LuaState, NumArgs, a_NumResults, -NumArgs - 2); if (s != 0) { // The error has already been printed together with the stacktrace LOGWARNING("Error in %s calling function %s()", m_SubsystemName.c_str(), CurrentFunctionName.c_str()); // Remove the error handler and error message from the stack: auto top = lua_gettop(m_LuaState); if (top < 2) { LogStackValues(Printf("The Lua stack is in an unexpected state, expected at least two values there, but got %d", top).c_str()); } lua_pop(m_LuaState, std::min(2, top)); return false; } // Remove the error handler from the stack: lua_remove(m_LuaState, -a_NumResults - 1); return true; } bool cLuaState::CheckParamUserTable(int a_StartParam, const char * a_UserTable, int a_EndParam) { ASSERT(IsValid()); if (a_EndParam < 0) { a_EndParam = a_StartParam; } tolua_Error tolua_err; for (int i = a_StartParam; i <= a_EndParam; i++) { if (tolua_isusertable(m_LuaState, i, a_UserTable, 0, &tolua_err)) { continue; } // Not the correct parameter lua_Debug entry; VERIFY(lua_getstack(m_LuaState, 0, &entry)); VERIFY(lua_getinfo (m_LuaState, "n", &entry)); AString ErrMsg = Printf("#ferror in function '%s'.", (entry.name != nullptr) ? entry.name : "?"); tolua_error(m_LuaState, ErrMsg.c_str(), &tolua_err); return false; } // for i - Param // All params checked ok return true; } bool cLuaState::CheckParamUserType(int a_StartParam, const char * a_UserType, int a_EndParam) { ASSERT(IsValid()); if (a_EndParam < 0) { a_EndParam = a_StartParam; } tolua_Error tolua_err; for (int i = a_StartParam; i <= a_EndParam; i++) { if (tolua_isusertype(m_LuaState, i, a_UserType, 0, &tolua_err)) { continue; } // Not the correct parameter lua_Debug entry; VERIFY(lua_getstack(m_LuaState, 0, &entry)); VERIFY(lua_getinfo (m_LuaState, "n", &entry)); AString ErrMsg = Printf("#ferror in function '%s'.", (entry.name != nullptr) ? entry.name : "?"); tolua_error(m_LuaState, ErrMsg.c_str(), &tolua_err); return false; } // for i - Param // All params checked ok return true; } bool cLuaState::CheckParamTable(int a_StartParam, int a_EndParam) { ASSERT(IsValid()); if (a_EndParam < 0) { a_EndParam = a_StartParam; } tolua_Error tolua_err; for (int i = a_StartParam; i <= a_EndParam; i++) { if (tolua_istable(m_LuaState, i, 0, &tolua_err)) { continue; } // Not the correct parameter lua_Debug entry; VERIFY(lua_getstack(m_LuaState, 0, &entry)); VERIFY(lua_getinfo (m_LuaState, "n", &entry)); AString ErrMsg = Printf("#ferror in function '%s'.", (entry.name != nullptr) ? entry.name : "?"); BreakIntoDebugger(m_LuaState); tolua_error(m_LuaState, ErrMsg.c_str(), &tolua_err); return false; } // for i - Param // All params checked ok return true; } bool cLuaState::CheckParamNumber(int a_StartParam, int a_EndParam) { ASSERT(IsValid()); if (a_EndParam < 0) { a_EndParam = a_StartParam; } tolua_Error tolua_err; for (int i = a_StartParam; i <= a_EndParam; i++) { if (tolua_isnumber(m_LuaState, i, 0, &tolua_err)) { continue; } // Not the correct parameter lua_Debug entry; VERIFY(lua_getstack(m_LuaState, 0, &entry)); VERIFY(lua_getinfo (m_LuaState, "n", &entry)); AString ErrMsg = Printf("#ferror in function '%s'.", (entry.name != nullptr) ? entry.name : "?"); tolua_error(m_LuaState, ErrMsg.c_str(), &tolua_err); return false; } // for i - Param // All params checked ok return true; } bool cLuaState::CheckParamBool(int a_StartParam, int a_EndParam) { ASSERT(IsValid()); if (a_EndParam < 0) { a_EndParam = a_StartParam; } tolua_Error tolua_err; for (int i = a_StartParam; i <= a_EndParam; i++) { if (tolua_isboolean(m_LuaState, i, 0, &tolua_err)) { continue; } // Not the correct parameter lua_Debug entry; VERIFY(lua_getstack(m_LuaState, 0, &entry)); VERIFY(lua_getinfo (m_LuaState, "n", &entry)); AString ErrMsg = Printf("#ferror in function '%s'.", (entry.name != nullptr) ? entry.name : "?"); tolua_error(m_LuaState, ErrMsg.c_str(), &tolua_err); return false; } // for i - Param // All params checked ok return true; } bool cLuaState::CheckParamString(int a_StartParam, int a_EndParam) { ASSERT(IsValid()); if (a_EndParam < 0) { a_EndParam = a_StartParam; } tolua_Error tolua_err; for (int i = a_StartParam; i <= a_EndParam; i++) { if (lua_isstring(m_LuaState, i)) { continue; } // Not the correct parameter lua_Debug entry; VERIFY(lua_getstack(m_LuaState, 0, &entry)); VERIFY(lua_getinfo (m_LuaState, "n", &entry)); tolua_err.array = 0; tolua_err.type = "string"; tolua_err.index = i; AString ErrMsg = Printf("#ferror in function '%s'.", (entry.name != nullptr) ? entry.name : "?"); tolua_error(m_LuaState, ErrMsg.c_str(), &tolua_err); return false; } // for i - Param // All params checked ok return true; } bool cLuaState::CheckParamFunction(int a_StartParam, int a_EndParam) { ASSERT(IsValid()); if (a_EndParam < 0) { a_EndParam = a_StartParam; } for (int i = a_StartParam; i <= a_EndParam; i++) { if (lua_isfunction(m_LuaState, i)) { continue; } // Not the correct parameter lua_Debug entry; VERIFY(lua_getstack(m_LuaState, 0, &entry)); VERIFY(lua_getinfo (m_LuaState, "n", &entry)); luaL_error(m_LuaState, "Error in function '%s' parameter #%d. Function expected, got %s", (entry.name != nullptr) ? entry.name : "?", i, GetTypeText(i).c_str() ); return false; } // for i - Param // All params checked ok return true; } bool cLuaState::CheckParamFunctionOrNil(int a_StartParam, int a_EndParam) { ASSERT(IsValid()); if (a_EndParam < 0) { a_EndParam = a_StartParam; } for (int i = a_StartParam; i <= a_EndParam; i++) { if (lua_isfunction(m_LuaState, i) || lua_isnil(m_LuaState, i)) { continue; } // Not the correct parameter lua_Debug entry; VERIFY(lua_getstack(m_LuaState, 0, &entry)); VERIFY(lua_getinfo (m_LuaState, "n", &entry)); luaL_error(m_LuaState, "Error in function '%s' parameter #%d. Function expected, got %s", (entry.name != nullptr) ? entry.name : "?", i, GetTypeText(i).c_str() ); return false; } // for i - Param // All params checked ok return true; } bool cLuaState::CheckParamVector3(int a_StartParam, int a_EndParam) { ASSERT(IsValid()); if (a_EndParam < 0) { a_EndParam = a_StartParam; } for (int i = a_StartParam; i <= a_EndParam; ++i) { if (IsParamVector3(a_StartParam)) { continue; } ApiParamError("Failed to read parameter #%d. Vector3 expected, got %s", i, GetTypeText(i).c_str()); return false; } return true; } bool cLuaState::CheckParamUUID(int a_StartParam, int a_EndParam) { ASSERT(IsValid()); if (a_EndParam < 0) { a_EndParam = a_StartParam; } cUUID tempUUID; AString tempStr; // Accept either a cUUID or a string that contains a valid UUID for (int i = a_StartParam; i <= a_EndParam; ++i) { tolua_Error err; if (tolua_isusertype(m_LuaState, i, "cUUID", 0, &err) && !lua_isnil(m_LuaState, i)) { continue; } if (!tolua_iscppstring(m_LuaState, i, 0, &err)) { ApiParamError("Failed to read parameter #%d. UUID expected, got %s", i, GetTypeText(i).c_str()); return false; } // Check string is a valid UUID GetStackValue(i, tempStr); if (!tempUUID.FromString(tempStr)) { ApiParamError("Failed to read parameter #%d. UUID expected, got non-UUID string:\n\t\"%s\"", i, tempStr.c_str()); return false; } } return true; } bool cLuaState::CheckParamEnd(int a_Param) { tolua_Error tolua_err; if (tolua_isnoobj(m_LuaState, a_Param, &tolua_err) == 1) { return true; } // Not the correct parameter lua_Debug entry; VERIFY(lua_getstack(m_LuaState, 0, &entry)); VERIFY(lua_getinfo (m_LuaState, "n", &entry)); AString ErrMsg = Printf("#ferror in function '%s': Too many arguments.", (entry.name != nullptr) ? entry.name : "?"); tolua_error(m_LuaState, ErrMsg.c_str(), &tolua_err); return false; } bool cLuaState::CheckParamSelf(const char * a_SelfClassName) { tolua_Error tolua_err; if (tolua_isusertype(m_LuaState, 1, a_SelfClassName, 0, &tolua_err) && !lua_isnil(m_LuaState, 1)) { return true; } // Not the correct parameter lua_Debug entry; VERIFY(lua_getstack(m_LuaState, 0, &entry)); VERIFY(lua_getinfo (m_LuaState, "n", &entry)); AString ErrMsg = Printf( "Error in function '%s'. The 'self' parameter is not of the expected type, \"instance of %s\". " \ "Make sure you're using the correct calling convention (obj:fn() instead of obj.fn()).", (entry.name != nullptr) ? entry.name : "", a_SelfClassName ); tolua_error(m_LuaState, ErrMsg.c_str(), &tolua_err); return false; } bool cLuaState::CheckParamStaticSelf(const char * a_SelfClassName) { tolua_Error tolua_err; if (tolua_isusertable(m_LuaState, 1, a_SelfClassName, 0, &tolua_err) && !lua_isnil(m_LuaState, 1)) { return true; } // Not the correct parameter lua_Debug entry; VERIFY(lua_getstack(m_LuaState, 0, &entry)); VERIFY(lua_getinfo (m_LuaState, "n", &entry)); AString ErrMsg = Printf( "Error in function '%s'. The 'self' parameter is not of the expected type, \"class %s\". " \ "Make sure you're using the correct calling convention (cClassName:fn() instead of cClassName.fn() or obj:fn()).", (entry.name != nullptr) ? entry.name : "", a_SelfClassName ); tolua_error(m_LuaState, ErrMsg.c_str(), &tolua_err); return false; } bool cLuaState::IsParamUserType(int a_ParamIdx, const AString & a_UserType) { ASSERT(IsValid()); tolua_Error tolua_err; return (tolua_isusertype(m_LuaState, a_ParamIdx, a_UserType.c_str(), 0, &tolua_err) == 1); } bool cLuaState::IsParamNumber(int a_ParamIdx) { ASSERT(IsValid()); tolua_Error tolua_err; return (tolua_isnumber(m_LuaState, a_ParamIdx, 0, &tolua_err) == 1); } bool cLuaState::IsParamVector3(int a_ParamIdx) { ASSERT(IsValid()); return ( IsParamUserType(a_ParamIdx, "Vector3") || IsParamUserType(a_ParamIdx, "Vector3") || IsParamUserType(a_ParamIdx, "Vector3") || lua_istable(m_LuaState, a_ParamIdx) // Assume any table is good enough ); } bool cLuaState::ReportErrors(int a_Status) { return ReportErrors(m_LuaState, a_Status); } bool cLuaState::ReportErrors(lua_State * a_LuaState, int a_Status) { if (a_Status == 0) { // No error to report return false; } LOGWARNING("LUA: %d - %s", a_Status, lua_tostring(a_LuaState, -1)); lua_pop(a_LuaState, 1); return true; } void cLuaState::LogStackTrace(int a_StartingDepth) { LogStackTrace(m_LuaState, a_StartingDepth); } void cLuaState::LogStackTrace(lua_State * a_LuaState, int a_StartingDepth) { LOGWARNING("Stack trace:"); lua_Debug entry; int depth = a_StartingDepth; while (lua_getstack(a_LuaState, depth, &entry)) { lua_getinfo(a_LuaState, "Sln", &entry); LOGWARNING(" %s(%d): %s", entry.short_src, entry.currentline, entry.name ? entry.name : "(no name)"); depth++; } LOGWARNING("Stack trace end"); } int cLuaState::ApiParamError(std::string_view a_Msg) { // Retrieve current function name lua_Debug entry; VERIFY(lua_getstack(m_LuaState, 0, &entry)); VERIFY(lua_getinfo(m_LuaState, "n", &entry)); // Compose the error message: AString errorMsg = fmt::format("{0}: {1}", (entry.name != nullptr) ? entry.name : "", a_Msg); // Log everything into the console: LOGWARNING("%s", errorMsg.c_str()); // cLuaState::LogStackTrace(a_LuaState); // Do NOT log stack trace, it is already output as part of the Lua error handling LogStackValues(m_LuaState, "Parameters on the stack"); // Raise Lua error: lua_pushstring(m_LuaState, errorMsg.c_str()); return lua_error(m_LuaState); } AString cLuaState::GetTypeText(int a_StackPos) { return lua_typename(m_LuaState, lua_type(m_LuaState, a_StackPos)); } int cLuaState::CallFunctionWithForeignParams( const AString & a_FunctionName, cLuaState & a_SrcLuaState, int a_SrcParamStart, int a_SrcParamEnd ) { ASSERT(IsValid()); ASSERT(a_SrcLuaState.IsValid()); // Store the stack position before any changes int OldTop = lua_gettop(m_LuaState); // Push the function to call, including the error handler: if (!PushFunction(a_FunctionName.c_str())) { LOGWARNING("Function '%s' not found", a_FunctionName.c_str()); lua_settop(m_LuaState, OldTop); return -1; } // Copy the function parameters to the target state if (CopyStackFrom(a_SrcLuaState, a_SrcParamStart, a_SrcParamEnd) < 0) { // Something went wrong, fix the stack and exit lua_settop(m_LuaState, OldTop); m_NumCurrentFunctionArgs = -1; m_CurrentFunctionName.clear(); return -1; } // Call the function, with an error handler: int s = lua_pcall(m_LuaState, a_SrcParamEnd - a_SrcParamStart + 1, LUA_MULTRET, OldTop + 1); if (ReportErrors(s)) { LOGWARN("Error while calling function '%s' in '%s'", a_FunctionName.c_str(), m_SubsystemName.c_str()); // Reset the stack: lua_settop(m_LuaState, OldTop); // Reset the internal checking mechanisms: m_NumCurrentFunctionArgs = -1; m_CurrentFunctionName.clear(); // Make Lua think everything is okay and return 0 values, so that plugins continue executing. // The failure is indicated by the zero return values. return 0; } // Reset the internal checking mechanisms: m_NumCurrentFunctionArgs = -1; m_CurrentFunctionName.clear(); // Remove the error handler from the stack: lua_remove(m_LuaState, OldTop + 1); // Return the number of return values: return lua_gettop(m_LuaState) - OldTop; } int cLuaState::CopyStackFrom(cLuaState & a_SrcLuaState, int a_SrcStart, int a_SrcEnd, int a_NumAllowedNestingLevels) { /* // DEBUG: LOGD("Copying stack values from %d to %d", a_SrcStart, a_SrcEnd); a_SrcLuaState.LogStack("Src stack before copying:"); LogStack("Dst stack before copying:"); */ for (int i = a_SrcStart; i <= a_SrcEnd; ++i) { if (!CopySingleValueFrom(a_SrcLuaState, i, a_NumAllowedNestingLevels)) { lua_pop(m_LuaState, i - a_SrcStart); return -1; } } return a_SrcEnd - a_SrcStart + 1; } bool cLuaState::CopyTableFrom(cLuaState & a_SrcLuaState, int a_SrcStackIdx, int a_NumAllowedNestingLevels) { [[maybe_unused]] const auto srcTop = lua_gettop(a_SrcLuaState); [[maybe_unused]] const auto dstTop = lua_gettop(m_LuaState); // Create the dest table: lua_createtable(m_LuaState, 0, 0); // DST:
lua_pushvalue(a_SrcLuaState, a_SrcStackIdx); // SRC:
lua_pushnil(a_SrcLuaState); // SRC:
while (lua_next(a_SrcLuaState, -2) != 0) // SRC:
{ ASSERT(lua_gettop(a_SrcLuaState) == srcTop + 3); ASSERT(lua_gettop(m_LuaState) == dstTop + 1); // Copy the key: if (!CopySingleValueFrom(a_SrcLuaState, -2, a_NumAllowedNestingLevels)) // DST:
{ lua_pop(m_LuaState, 1); lua_pop(a_SrcLuaState, 3); ASSERT(lua_gettop(a_SrcLuaState) == srcTop); ASSERT(lua_gettop(m_LuaState) == dstTop); return false; } ASSERT(lua_gettop(a_SrcLuaState) == srcTop + 3); ASSERT(lua_gettop(m_LuaState) == dstTop + 2); // Copy the value: if (!CopySingleValueFrom(a_SrcLuaState, -1, a_NumAllowedNestingLevels - 1)) // DST:
{ lua_pop(m_LuaState, 2); // DST: empty lua_pop(a_SrcLuaState, 3); // SRC: empty ASSERT(lua_gettop(a_SrcLuaState) == srcTop); ASSERT(lua_gettop(m_LuaState) == dstTop); return false; } ASSERT(lua_gettop(a_SrcLuaState) == srcTop + 3); ASSERT(lua_gettop(m_LuaState) == dstTop + 3); // Set the value and fix up stacks: lua_rawset(m_LuaState, -3); // DST:
lua_pop(a_SrcLuaState, 1); // SRC:
ASSERT(lua_gettop(a_SrcLuaState) == srcTop + 2); ASSERT(lua_gettop(m_LuaState) == dstTop + 1); } lua_pop(a_SrcLuaState, 1); // SRC: empty ASSERT(lua_gettop(a_SrcLuaState) == srcTop); ASSERT(lua_gettop(m_LuaState) == dstTop + 1); return true; } bool cLuaState::CopySingleValueFrom(cLuaState & a_SrcLuaState, int a_StackIdx, int a_NumAllowedNestingLevels) { int t = lua_type(a_SrcLuaState, a_StackIdx); switch (t) { case LUA_TNIL: { lua_pushnil(m_LuaState); return true; } case LUA_TSTRING: { AString s; a_SrcLuaState.ToString(a_StackIdx, s); Push(s); return true; } case LUA_TBOOLEAN: { bool b = (tolua_toboolean(a_SrcLuaState, a_StackIdx, false) != 0); Push(b); return true; } case LUA_TNUMBER: { lua_Number d = tolua_tonumber(a_SrcLuaState, a_StackIdx, 0); Push(d); return true; } case LUA_TUSERDATA: { // Get the class name: const char * type = nullptr; if (lua_getmetatable(a_SrcLuaState, a_StackIdx) == 0) { LOGWARNING("%s: Unknown class in pos %d, cannot copy.", __FUNCTION__, a_StackIdx); return false; } lua_rawget(a_SrcLuaState, LUA_REGISTRYINDEX); // Stack +1 type = lua_tostring(a_SrcLuaState, -1); lua_pop(a_SrcLuaState, 1); // Stack -1 // Copy the value: void * ud = tolua_touserdata(a_SrcLuaState, a_StackIdx, nullptr); tolua_pushusertype(m_LuaState, ud, type); return true; } case LUA_TTABLE: { if (!CopyTableFrom(a_SrcLuaState, a_StackIdx, a_NumAllowedNestingLevels - 1)) { LOGWARNING("%s: Failed to copy table in pos %d.", __FUNCTION__, a_StackIdx); return false; } return true; } default: { LOGWARNING("%s: Unsupported value: '%s' at stack position %d. Can only copy numbers, strings, bools, classes and simple tables!", __FUNCTION__, lua_typename(a_SrcLuaState, t), a_StackIdx ); return false; } } } void cLuaState::ToString(int a_StackPos, AString & a_String) { size_t len; const char * s = lua_tolstring(m_LuaState, a_StackPos, &len); if (s != nullptr) { a_String.assign(s, len); } } void cLuaState::LogStackValues(const char * a_Header) { LogStackValues(m_LuaState, a_Header); } void cLuaState::LogStackValues(lua_State * a_LuaState, const char * a_Header) { // Format string consisting only of %s is used to appease the compiler ASSERT_LUA_STACK_BALANCE(a_LuaState, false); LOG("%s", (a_Header != nullptr) ? a_Header : "Lua C API Stack contents:"); for (int i = lua_gettop(a_LuaState); i > 0; i--) { AString Value; int Type = lua_type(a_LuaState, i); switch (Type) { case LUA_TBOOLEAN: Value.assign((lua_toboolean(a_LuaState, i) != 0) ? "true" : "false"); break; case LUA_TLIGHTUSERDATA: Printf(Value, "%p", lua_touserdata(a_LuaState, i)); break; case LUA_TNUMBER: Printf(Value, "%f", static_cast(lua_tonumber(a_LuaState, i))); break; case LUA_TSTRING: { size_t len; const char * txt = lua_tolstring(a_LuaState, i, &len); Value.assign(txt, std::min(len, 50)); // Only log up to 50 characters of the string break; } case LUA_TTABLE: Printf(Value, "%p", lua_topointer(a_LuaState, i)); break; case LUA_TFUNCTION: Printf(Value, "%p", lua_topointer(a_LuaState, i)); break; case LUA_TUSERDATA: { Printf(Value, "%p (%s)", lua_touserdata(a_LuaState, i), tolua_typename(a_LuaState, i)); // tolua_typename pushes the string onto Lua stack, pop it off again: lua_pop(a_LuaState, 1); break; } default: break; } LOGD(" Idx %d: type %d (%s) %s", i, Type, lua_typename(a_LuaState, Type), Value.c_str()); } // for i - stack idx } cLuaState * cLuaState::QueryCanonLuaState(void) const { return cCanonLuaStates::GetCanonState(m_LuaState); } void cLuaState::LogApiCallParamFailure(const char * a_FnName, const char * a_ParamNames) { LOGWARNING("%s: Cannot read params: %s, bailing out.", a_FnName, a_ParamNames); LogStackTrace(); LogStackValues("Values on the stack"); } void cLuaState::TrackInDeadlockDetect(cDeadlockDetect & a_DeadlockDetect) { a_DeadlockDetect.TrackCriticalSection(m_CS, Printf("cLuaState %s", m_SubsystemName.c_str())); } void cLuaState::UntrackInDeadlockDetect(cDeadlockDetect & a_DeadlockDetect) { a_DeadlockDetect.UntrackCriticalSection(m_CS); } int cLuaState::ReportFnCallErrors(lua_State * a_LuaState) { LOGWARNING("LUA: %s", lua_tostring(a_LuaState, -1)); LogStackTrace(a_LuaState, 1); BreakIntoDebugger(a_LuaState); return 1; // We left the error message on the stack as the return value } int cLuaState::BreakIntoDebugger(lua_State * a_LuaState) { ASSERT_LUA_STACK_BALANCE(a_LuaState); // Call the BreakIntoDebugger function, if available: lua_getglobal(a_LuaState, "BreakIntoDebugger"); if (!lua_isfunction(a_LuaState, -1)) { LOGD("LUA: BreakIntoDebugger() not found / not a function"); lua_pop(a_LuaState, 1); return 1; } lua_pushvalue(a_LuaState, -2); // Copy the string that has been passed to us LOGD("Calling BreakIntoDebugger()..."); lua_call(a_LuaState, 1, 0); LOGD("Returned from BreakIntoDebugger()."); return 0; } void cLuaState::TrackRef(cTrackedRef & a_Ref) { // Get the CanonLuaState global from Lua: auto canonState = QueryCanonLuaState(); if (canonState == nullptr) { LOGWARNING("%s: Lua state %p has invalid CanonLuaState!", __FUNCTION__, static_cast(m_LuaState)); return; } // Add the callback: cCSLock Lock(canonState->m_CSTrackedRefs); canonState->m_TrackedRefs.push_back(&a_Ref); } void cLuaState::UntrackRef(cTrackedRef & a_Ref) { // Get the CanonLuaState global from Lua: auto canonState = QueryCanonLuaState(); if (canonState == nullptr) { LOGWARNING("%s: Lua state %p has invalid CanonLuaState!", __FUNCTION__, static_cast(m_LuaState)); return; } // Remove the callback (note that another thread may have cleared the callbacks by closing the LuaState): cCSLock Lock(canonState->m_CSTrackedRefs); auto & trackedRefs = canonState->m_TrackedRefs; for (auto itr = trackedRefs.begin(), end = trackedRefs.end(); itr != end; ++itr) { if (*itr == &a_Ref) { trackedRefs.erase(itr); break; } } } //////////////////////////////////////////////////////////////////////////////// // cLuaState::cRef: cLuaState::cRef::cRef(void) : m_LuaState(nullptr), m_Ref(LUA_REFNIL) { } cLuaState::cRef::cRef(cLuaState & a_LuaState, int a_StackPos) : m_LuaState(nullptr), m_Ref(LUA_REFNIL) { RefStack(a_LuaState, a_StackPos); } cLuaState::cRef::cRef(cRef && a_FromRef): m_LuaState(a_FromRef.m_LuaState), m_Ref(a_FromRef.m_Ref) { a_FromRef.m_LuaState = nullptr; a_FromRef.m_Ref = LUA_REFNIL; } cLuaState::cRef::~cRef() { if (m_LuaState != nullptr) { UnRef(); } } void cLuaState::cRef::RefStack(cLuaState & a_LuaState, int a_StackPos) { ASSERT(a_LuaState.IsValid()); if (m_LuaState != nullptr) { UnRef(); } ASSERT(cCanonLuaStates::GetCanonState(a_LuaState)->m_CS.IsLockedByCurrentThread()); m_LuaState = a_LuaState; lua_pushvalue(a_LuaState, a_StackPos); // Push a copy of the value at a_StackPos onto the stack m_Ref = luaL_ref(a_LuaState, LUA_REGISTRYINDEX); } void cLuaState::cRef::UnRef(void) { if (IsValid()) { ASSERT(cCanonLuaStates::GetCanonState(m_LuaState)->m_CS.IsLockedByCurrentThread()); luaL_unref(m_LuaState, LUA_REGISTRYINDEX, m_Ref); } m_LuaState = nullptr; m_Ref = LUA_REFNIL; }