diff --git a/src/Bindings/BindingsProcessor.lua b/src/Bindings/BindingsProcessor.lua index f936d3a68..df479a634 100644 --- a/src/Bindings/BindingsProcessor.lua +++ b/src/Bindings/BindingsProcessor.lua @@ -234,7 +234,6 @@ local function OutputLuaStateHelpers(a_Package) if not(g_HasCustomPushImplementation[item.name]) then f:write("void cLuaState::Push(" .. item.name .. " * a_Value)\n{\n\tASSERT(IsValid());\n") f:write("\ttolua_pushusertype(m_LuaState, a_Value, \"" .. item.name .. "\");\n"); - f:write("\tm_NumCurrentFunctionArgs += 1;\n") f:write("}\n\n\n\n\n\n") end end diff --git a/src/Bindings/LuaJson.cpp b/src/Bindings/LuaJson.cpp index 39a720319..7a0ddb961 100644 --- a/src/Bindings/LuaJson.cpp +++ b/src/Bindings/LuaJson.cpp @@ -82,7 +82,7 @@ void PushJsonValue(const Json::Value & a_Value, cLuaState & a_LuaState) { case Json::nullValue: { - a_LuaState.PushNil(); + a_LuaState.Push(cLuaState::Nil); break; } @@ -228,8 +228,7 @@ static int tolua_cJson_Parse(lua_State * a_LuaState) Json::Reader reader; if (!reader.parse(input, root, false)) { - L.PushNil(); - L.Push(Printf("Parsing Json failed: %s", reader.getFormattedErrorMessages().c_str())); + L.Push(cLuaState::Nil, Printf("Parsing Json failed: %s", reader.getFormattedErrorMessages().c_str())); return 2; } diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp index fd29e8e34..ec6bdb48a 100644 --- a/src/Bindings/LuaState.cpp +++ b/src/Bindings/LuaState.cpp @@ -42,6 +42,7 @@ extern "C" const cLuaState::cRet cLuaState::Return = {}; +const cLuaState::cNil cLuaState::Nil = {}; /** Each Lua state stores a pointer to its creating cLuaState in Lua globals, under this name. This way any cLuaState can reference the main cLuaState's TrackedCallbacks, mutex etc. */ @@ -341,6 +342,33 @@ void cLuaState::cStackTable::ForEachArrayElement(std::function a_ElementCallback) const +{ + #ifdef _DEBUG + auto stackTop = lua_gettop(m_LuaState); + #endif + 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: @@ -724,24 +752,29 @@ bool cLuaState::PushFunction(const cRef & a_TableRef, const char * a_FnName) -void cLuaState::PushNil(void) +void cLuaState::Push(const AString & a_String) { ASSERT(IsValid()); - lua_pushnil(m_LuaState); - m_NumCurrentFunctionArgs += 1; + lua_pushlstring(m_LuaState, a_String.data(), a_String.size()); } -void cLuaState::Push(const AString & a_String) +void cLuaState::Push(const AStringMap & a_Dictionary) { ASSERT(IsValid()); - lua_pushlstring(m_LuaState, a_String.data(), a_String.size()); - m_NumCurrentFunctionArgs += 1; + 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); + } } @@ -760,7 +793,6 @@ void cLuaState::Push(const AStringVector & a_Vector) tolua_pushstring(m_LuaState, itr->c_str()); lua_rawseti(m_LuaState, newTable, index); } - m_NumCurrentFunctionArgs += 1; } @@ -772,7 +804,6 @@ void cLuaState::Push(const cCraftingGrid * a_Grid) ASSERT(IsValid()); tolua_pushusertype(m_LuaState, reinterpret_cast(const_cast(a_Grid)), "cCraftingGrid"); - m_NumCurrentFunctionArgs += 1; } @@ -784,7 +815,6 @@ void cLuaState::Push(const cCraftingRecipe * a_Recipe) ASSERT(IsValid()); tolua_pushusertype(m_LuaState, reinterpret_cast(const_cast(a_Recipe)), "cCraftingRecipe"); - m_NumCurrentFunctionArgs += 1; } @@ -796,7 +826,6 @@ void cLuaState::Push(const char * a_Value) ASSERT(IsValid()); tolua_pushstring(m_LuaState, a_Value); - m_NumCurrentFunctionArgs += 1; } @@ -808,7 +837,17 @@ void cLuaState::Push(const cItems & a_Items) ASSERT(IsValid()); tolua_pushusertype(m_LuaState, reinterpret_cast(const_cast(&a_Items)), "cItems"); - m_NumCurrentFunctionArgs += 1; +} + + + + + +void cLuaState::Push(const cNil & a_Nil) +{ + ASSERT(IsValid()); + + lua_pushnil(m_LuaState); } @@ -820,7 +859,6 @@ void cLuaState::Push(const cPlayer * a_Player) ASSERT(IsValid()); tolua_pushusertype(m_LuaState, reinterpret_cast(const_cast(a_Player)), "cPlayer"); - m_NumCurrentFunctionArgs += 1; } @@ -832,7 +870,6 @@ void cLuaState::Push(const cLuaState::cRef & a_Ref) ASSERT(IsValid()); lua_rawgeti(m_LuaState, LUA_REGISTRYINDEX, static_cast(a_Ref)); - m_NumCurrentFunctionArgs += 1; } @@ -844,7 +881,6 @@ void cLuaState::Push(const HTTPRequest * a_Request) ASSERT(IsValid()); tolua_pushusertype(m_LuaState, reinterpret_cast(const_cast(a_Request)), "HTTPRequest"); - m_NumCurrentFunctionArgs += 1; } @@ -856,7 +892,6 @@ void cLuaState::Push(const HTTPTemplateRequest * a_Request) ASSERT(IsValid()); tolua_pushusertype(m_LuaState, reinterpret_cast(const_cast(a_Request)), "HTTPTemplateRequest"); - m_NumCurrentFunctionArgs += 1; } @@ -868,7 +903,6 @@ void cLuaState::Push(const Vector3d & a_Vector) ASSERT(IsValid()); tolua_pushusertype(m_LuaState, reinterpret_cast(const_cast(&a_Vector)), "Vector3"); - m_NumCurrentFunctionArgs += 1; } @@ -880,7 +914,6 @@ void cLuaState::Push(const Vector3d * a_Vector) ASSERT(IsValid()); tolua_pushusertype(m_LuaState, reinterpret_cast(const_cast(a_Vector)), "Vector3"); - m_NumCurrentFunctionArgs += 1; } @@ -892,7 +925,6 @@ void cLuaState::Push(const Vector3i & a_Vector) ASSERT(IsValid()); tolua_pushusertype(m_LuaState, reinterpret_cast(const_cast(&a_Vector)), "Vector3"); - m_NumCurrentFunctionArgs += 1; } @@ -904,7 +936,6 @@ void cLuaState::Push(const Vector3i * a_Vector) ASSERT(IsValid()); tolua_pushusertype(m_LuaState, reinterpret_cast(const_cast(a_Vector)), "Vector3"); - m_NumCurrentFunctionArgs += 1; } @@ -916,7 +947,6 @@ void cLuaState::Push(bool a_Value) ASSERT(IsValid()); tolua_pushboolean(m_LuaState, a_Value ? 1 : 0); - m_NumCurrentFunctionArgs += 1; } @@ -981,8 +1011,6 @@ void cLuaState::Push(cEntity * a_Entity) } } // switch (EntityType) } - - m_NumCurrentFunctionArgs += 1; } @@ -994,7 +1022,6 @@ void cLuaState::Push(cLuaServerHandle * a_ServerHandle) ASSERT(IsValid()); tolua_pushusertype(m_LuaState, a_ServerHandle, "cServerHandle"); - m_NumCurrentFunctionArgs += 1; } @@ -1006,7 +1033,6 @@ void cLuaState::Push(cLuaTCPLink * a_TCPLink) ASSERT(IsValid()); tolua_pushusertype(m_LuaState, a_TCPLink, "cTCPLink"); - m_NumCurrentFunctionArgs += 1; } @@ -1018,7 +1044,6 @@ void cLuaState::Push(cLuaUDPEndpoint * a_UDPEndpoint) ASSERT(IsValid()); tolua_pushusertype(m_LuaState, a_UDPEndpoint, "cUDPEndpoint"); - m_NumCurrentFunctionArgs += 1; } @@ -1030,7 +1055,6 @@ void cLuaState::Push(double a_Value) ASSERT(IsValid()); tolua_pushnumber(m_LuaState, a_Value); - m_NumCurrentFunctionArgs += 1; } @@ -1042,7 +1066,6 @@ void cLuaState::Push(int a_Value) ASSERT(IsValid()); tolua_pushnumber(m_LuaState, a_Value); - m_NumCurrentFunctionArgs += 1; } @@ -1054,7 +1077,6 @@ void cLuaState::Push(long a_Value) ASSERT(IsValid()); tolua_pushnumber(m_LuaState, static_cast(a_Value)); - m_NumCurrentFunctionArgs += 1; } @@ -1066,7 +1088,6 @@ void cLuaState::Push(UInt32 a_Value) ASSERT(IsValid()); tolua_pushnumber(m_LuaState, a_Value); - m_NumCurrentFunctionArgs += 1; } @@ -1078,7 +1099,6 @@ void cLuaState::Push(std::chrono::milliseconds a_Value) ASSERT(IsValid()); tolua_pushnumber(m_LuaState, static_cast(a_Value.count())); - m_NumCurrentFunctionArgs += 1; } @@ -1090,7 +1110,6 @@ void cLuaState::Pop(int a_NumValuesToPop) ASSERT(IsValid()); lua_pop(m_LuaState, a_NumValuesToPop); - m_NumCurrentFunctionArgs -= a_NumValuesToPop; } @@ -1113,6 +1132,37 @@ bool cLuaState::GetStackValue(int a_StackPos, AString & a_Value) +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, bool & a_ReturnedVal) { a_ReturnedVal = (tolua_toboolean(m_LuaState, a_StackPos, a_ReturnedVal ? 1 : 0) > 0); diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index 60ae840b0..cb68b9a98 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -309,14 +309,39 @@ public: typedef UniquePtr cTableRefPtr; + /** Represents a parameter that is optional - calling a GetStackValue() with this object will not fail if the value on the Lua stack is nil. + Note that the GetStackValue() will still fail if the param is present but of a different type. + The class itself is just a marker so that the template magic will select the correct GetStackValue() overload. */ + template + class cOptionalParam + { + public: + explicit cOptionalParam(T & a_Dest): + m_Dest(a_Dest) + { + } + + T & GetDest(void) { return m_Dest; } + + protected: + T & m_Dest; + }; + + /** A dummy class that's used only to delimit function args from return values for cLuaState::Call() */ class cRet { } ; - static const cRet Return; // Use this constant to delimit function args from return values for cLuaState::Call() + /** A dummy class that's used only to push a constant nil as a function parameter in Call(). */ + class cNil + { + }; + static const cNil Nil; // Use this constant to give a function a nil parameter in Call() + + /** A RAII class for values pushed onto the Lua stack. Will pop the value off the stack in the destructor. */ class cStackValue @@ -471,15 +496,15 @@ public: Push(std::forward(a_Arg2), std::forward(a_Args)...); } - void PushNil(void); - // Push a const value onto the stack (keep alpha-sorted): void Push(const AString & a_String); + void Push(const AStringMap & a_Dictionary); void Push(const AStringVector & a_Vector); void Push(const cCraftingGrid * a_Grid); void Push(const cCraftingRecipe * a_Recipe); void Push(const char * a_Value); void Push(const cItems & a_Items); + void Push(const cNil & a_Nil); void Push(const cPlayer * a_Player); void Push(const cRef & a_Ref); void Push(const HTTPRequest * a_Request); @@ -508,6 +533,7 @@ public: // Returns whether value was changed // Enum values are checked for their allowed values and fail if the value is not assigned. bool GetStackValue(int a_StackPos, AString & a_Value); + bool GetStackValue(int a_StackPos, AStringMap & a_Value); bool GetStackValue(int a_StackPos, bool & a_Value); bool GetStackValue(int a_StackPos, cCallback & a_Callback); bool GetStackValue(int a_StackPos, cCallbackPtr & a_Callback); @@ -549,6 +575,17 @@ public: return true; } + /** Retrieves an optional value on the stack - doesn't fail if the stack contains nil instead of the value. */ + template + bool GetStackValue(int a_StackPos, cOptionalParam && a_ReturnedVal) + { + if (lua_isnoneornil(m_LuaState, a_StackPos)) + { + return true; + } + return GetStackValue(a_StackPos, a_ReturnedVal.GetDest()); + } + /** Pushes the named value in the table at the top of the stack. a_Name may be a path containing multiple table levels, such as "cChatColor.Blue". If the value is found, it is pushed on top of the stack and the returned cStackValue is valid. @@ -597,6 +634,7 @@ public: template bool Call(const FnT & a_Function, Args &&... args) { + m_NumCurrentFunctionArgs = -1; if (!PushFunction(std::forward(a_Function))) { // Pushing the function failed @@ -606,14 +644,14 @@ public: } /** Retrieves a list of values from the Lua stack, starting at the specified index. */ - template - inline bool GetStackValues(int a_StartStackPos, T & a_Ret, Args &&... args) + template + inline bool GetStackValues(int a_StartStackPos, Arg1 && a_Arg1, Args &&... args) { - if (!GetStackValue(a_StartStackPos, a_Ret)) + if (!GetStackValue(a_StartStackPos, std::forward(a_Arg1))) { return false; } - return GetStackValues(a_StartStackPos + 1, args...); + return GetStackValues(a_StartStackPos + 1, std::forward(args)...); } /** Returns true if the specified parameters on the stack are of the specified usertable type; also logs warning if not. Used for static functions */ @@ -758,6 +796,7 @@ protected: inline bool PushCallPop(T && a_Param, Args &&... args) { Push(std::forward(a_Param)); + m_NumCurrentFunctionArgs += 1; return PushCallPop(std::forward(args)...); } diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index 8927b0b19..038c67995 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -2058,8 +2058,7 @@ static int tolua_cUrlParser_Parse(lua_State * a_LuaState) if (!res.first) { // Error, return nil and error msg: - L.PushNil(); - L.Push(res.second); + L.Push(cLuaState::Nil, res.second); return 2; } L.Push(scheme, username, password, host, port, path, query, fragment); @@ -2099,8 +2098,7 @@ static int tolua_cUrlParser_ParseAuthorityPart(lua_State * a_LuaState) if (!res.first) { // Error, return nil and error msg: - L.PushNil(); - L.Push(res.second); + L.Push(cLuaState::Nil, res.second); return 2; } L.Push(username, password, host, port); diff --git a/src/Bindings/ManualBindings_Network.cpp b/src/Bindings/ManualBindings_Network.cpp index 68eba5870..c8565d23d 100644 --- a/src/Bindings/ManualBindings_Network.cpp +++ b/src/Bindings/ManualBindings_Network.cpp @@ -649,8 +649,7 @@ static int tolua_cTCPLink_StartTLSClient(lua_State * L) AString res = Link->StartTLSClient(OwnCert, OwnPrivKey, OwnPrivKeyPassword); if (!res.empty()) { - S.PushNil(); - S.Push(Printf("Cannot start TLS on link to %s:%d: %s", Link->GetRemoteIP().c_str(), Link->GetRemotePort(), res.c_str())); + S.Push(cLuaState::Nil, Printf("Cannot start TLS on link to %s:%d: %s", Link->GetRemoteIP().c_str(), Link->GetRemotePort(), res.c_str())); return 2; } return 1; @@ -695,10 +694,10 @@ static int tolua_cTCPLink_StartTLSServer(lua_State * L) AString res = Link->StartTLSServer(OwnCert, OwnPrivKey, OwnPrivKeyPassword, StartTLSData); if (!res.empty()) { - S.PushNil(); - S.Push(Printf("Cannot start TLS on link to %s:%d: %s", Link->GetRemoteIP().c_str(), Link->GetRemotePort(), res.c_str())); + S.Push(cLuaState::Nil, Printf("Cannot start TLS on link to %s:%d: %s", Link->GetRemoteIP().c_str(), Link->GetRemotePort(), res.c_str())); return 2; } + S.Push(true); return 1; }