From 5618e453e60219c10ac4d811dc44926ecee7c370 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Tue, 31 May 2016 01:01:55 +0200 Subject: LuaState: Inter-plugin calls now support simple tables. (#3220) --- Server/Plugins/APIDump/Classes/Plugins.lua | 2 +- Server/Plugins/Debuggers/Debuggers.lua | 57 +++++++++ Server/Plugins/Debuggers/Info.lua | 6 + src/Bindings/LuaState.cpp | 178 ++++++++++++++++++++--------- src/Bindings/LuaState.h | 16 ++- 5 files changed, 204 insertions(+), 55 deletions(-) diff --git a/Server/Plugins/APIDump/Classes/Plugins.lua b/Server/Plugins/APIDump/Classes/Plugins.lua index e053c57e2..d5ac5fdbd 100644 --- a/Server/Plugins/APIDump/Classes/Plugins.lua +++ b/Server/Plugins/APIDump/Classes/Plugins.lua @@ -65,7 +65,7 @@ cPluginManager.AddHook(cPluginManager.HOOK_CHAT, OnChatMessage); { Params = "Command, Callback, HelpString", Return = "[bool]", Notes = "(STATIC) Binds a console command with the specified callback function and help string. By common convention, providing an empty string for HelpString will hide the command from the \"help\" console command. Returns true if successful, logs to console and returns no value on error. The callback uses the following signature:
function(Split)
The Split parameter contains an array-table of the words that the admin has typed. If the callback returns true, the command is assumed to have executed successfully; in all other cases the server issues a warning to the console that the command is unknown (this is so that subcommands can be implemented)." }, { Params = "Command, Callback, HelpString", Return = "[bool]", Notes = "Binds a console command with the specified callback function and help string. By common convention, providing an empty string for HelpString will hide the command from the \"help\" console command. Returns true if successful, logs to console and returns no value on error. The callback uses the following signature:
function(Split)
The Split parameter contains an array-table of the words that the admin has typed. If the callback returns true, the command is assumed to have executed successfully; in all other cases the server issues a warning to the console that the command is unknown (this is so that subcommands can be implemented)." }, }, - CallPlugin = { Params = "PluginName, FunctionName, [FunctionArgs...]", Return = "[FunctionRets]", Notes = "(STATIC) Calls the specified function in the specified plugin, passing all the given arguments to it. If it succeeds, it returns all the values returned by that function. If it fails, returns no value at all. Note that only strings, numbers, bools, nils and classes can be used for parameters and return values; tables and functions cannot be copied across plugins." }, + CallPlugin = { Params = "PluginName, FunctionName, [FunctionArgs...]", Return = "[FunctionRets]", Notes = "(STATIC) Calls the specified function in the specified plugin, passing all the given arguments to it. If it succeeds, it returns all the values returned by that function. If it fails, returns no value at all. Note that only strings, numbers, bools, nils, API classes and simple tables can be used for parameters and return values; functions cannot be copied across plugins." }, DoWithPlugin = { Params = "PluginName, CallbackFn", Return = "bool", Notes = "(STATIC) Calls the CallbackFn for the specified plugin, if found. A plugin can be found even if it is currently unloaded, disabled or errored, the callback should check the plugin status. If the plugin is not found, this function returns false, otherwise it returns the bool value that the callback has returned. The CallbackFn has the following signature:
function ({{cPlugin|Plugin}})
" }, ExecuteCommand = { Params = "{{cPlayer|Player}}, CommandStr", Return = "{{cPluginManager#CommandResult|CommandResult}}", Notes = "Executes the command as if given by the specified Player. Checks permissions." }, ExecuteConsoleCommand = { Params = "CommandStr", Return = "bool, string", Notes = "Executes the console command as if given by the admin on the console. If the command is successfully executed, returns true and the text that would be output to the console normally. On error it returns false and an error message." }, diff --git a/Server/Plugins/Debuggers/Debuggers.lua b/Server/Plugins/Debuggers/Debuggers.lua index ae06b4fc9..aa2205368 100644 --- a/Server/Plugins/Debuggers/Debuggers.lua +++ b/Server/Plugins/Debuggers/Debuggers.lua @@ -1921,6 +1921,63 @@ end +function HandleConsoleTestCall(a_Split, a_EntireCmd) + LOG("Testing inter-plugin calls") + LOG("Note: These will fail if the Core plugin is not enabled") + + -- Test calling the HandleConsoleWeather handler: + local pm = cPluginManager + LOG("Calling Core's HandleConsoleWeather") + local isSuccess = pm:CallPlugin("Core", "HandleConsoleWeather", + { + "/weather", + "rain", + } + ) + if (type(isSuccess) == "boolean") then + LOG("Success") + else + LOG("FAILED") + end + + -- Test injecting some code: + LOG("Injecting code into the Core plugin") + isSuccess = pm:CallPlugin("Core", "dofile", pm:GetCurrentPlugin():GetLocalFolder() .. "/Inject.lua") + if (type(isSuccess) == "boolean") then + LOG("Success") + else + LOG("FAILED") + end + + -- Test the full capabilities of the table-passing API, using the injected function: + LOG("Calling injected code") + isSuccess = pm:CallPlugin("Core", "injectedPrintParams", + { + "test", + nil, + { + "test", + "test" + }, + [10] = "test", + ["test"] = "test", + [{"test"}] = "test", + [true] = "test", + } + ) + if (type(isSuccess) == "boolean") then + LOG("Success") + else + LOG("FAILED") + end + + return true +end + + + + + function HandleConsoleTestJson(a_Split, a_EntireCmd) LOG("Testing Json parsing...") local t1 = cJson:Parse([[{"a": 1, "b": "2", "c": [3, "4", 5] }]]) diff --git a/Server/Plugins/Debuggers/Info.lua b/Server/Plugins/Debuggers/Info.lua index fa85f0532..af3461342 100644 --- a/Server/Plugins/Debuggers/Info.lua +++ b/Server/Plugins/Debuggers/Info.lua @@ -254,6 +254,12 @@ g_PluginInfo = HelpString = "Tests the world scheduling", }, + ["testcall"] = + { + Handler = HandleConsoleTestCall, + HelpString = "Tests inter-plugin calls with various values" + }, + ["testjson"] = { Handler = HandleConsoleTestJson, diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp index 200878cf7..e0551c550 100644 --- a/src/Bindings/LuaState.cpp +++ b/src/Bindings/LuaState.cpp @@ -1470,7 +1470,7 @@ int cLuaState::CallFunctionWithForeignParams( -int cLuaState::CopyStackFrom(cLuaState & a_SrcLuaState, int a_SrcStart, int a_SrcEnd) +int cLuaState::CopyStackFrom(cLuaState & a_SrcLuaState, int a_SrcStart, int a_SrcEnd, int a_NumAllowedNestingLevels) { /* // DEBUG: @@ -1480,64 +1480,138 @@ int cLuaState::CopyStackFrom(cLuaState & a_SrcLuaState, int a_SrcStart, int a_Sr */ for (int i = a_SrcStart; i <= a_SrcEnd; ++i) { - int t = lua_type(a_SrcLuaState, i); - switch (t) + if (!CopySingleValueFrom(a_SrcLuaState, i, a_NumAllowedNestingLevels)) { - case LUA_TNIL: - { - lua_pushnil(m_LuaState); - break; - } - case LUA_TSTRING: - { - AString s; - a_SrcLuaState.ToString(i, s); - Push(s); - break; - } - case LUA_TBOOLEAN: - { - bool b = (tolua_toboolean(a_SrcLuaState, i, false) != 0); - Push(b); - break; - } - case LUA_TNUMBER: - { - lua_Number d = tolua_tonumber(a_SrcLuaState, i, 0); - Push(d); - break; - } - case LUA_TUSERDATA: + 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) +{ + // Create the dest table: + #ifdef _DEBUG + auto srcTop = lua_gettop(a_SrcLuaState); + auto dstTop = lua_gettop(m_LuaState); + #endif + 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) { - // Get the class name: - const char * type = nullptr; - if (lua_getmetatable(a_SrcLuaState, i) == 0) - { - LOGWARNING("%s: Unknown class in pos %d, cannot copy.", __FUNCTION__, i); - lua_pop(m_LuaState, i - a_SrcStart); - return -1; - } - 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, i, nullptr); - tolua_pushusertype(m_LuaState, ud, type); - break; + LOGWARNING("%s: Unknown class in pos %d, cannot copy.", __FUNCTION__, a_StackIdx); + return false; } - default: + 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: Unsupported value: '%s' at stack position %d. Can only copy numbers, strings, bools and classes!", - __FUNCTION__, lua_typename(a_SrcLuaState, t), i - ); - a_SrcLuaState.LogStack("Stack where copying failed:"); - lua_pop(m_LuaState, i - a_SrcStart); - return -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; } } - return a_SrcEnd - a_SrcStart + 1; } diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index 215980033..b795a80d4 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -419,10 +419,22 @@ public: ); /** Copies objects on the stack from the specified state. - Only numbers, bools, strings and userdatas are copied. + Only numbers, bools, strings, API classes and simple tables containing these (recursively) are copied. + a_NumAllowedNestingLevels specifies how many table nesting levels are allowed, copying fails if there's a deeper table. If successful, returns the number of objects copied. If failed, returns a negative number and rewinds the stack position. */ - int CopyStackFrom(cLuaState & a_SrcLuaState, int a_SrcStart, int a_SrcEnd); + int CopyStackFrom(cLuaState & a_SrcLuaState, int a_SrcStart, int a_SrcEnd, int a_NumAllowedNestingLevels = 16); + + /** Copies a table at the specified stack index of the source Lua state to the top of this Lua state's stack. + a_NumAllowedNestingLevels specifies how many table nesting levels are allowed, copying fails if there's a deeper table. + Returns true if successful, false on failure. + Can copy only simple values - numbers, bools, strings and recursively simple tables. */ + bool CopyTableFrom(cLuaState & a_SrcLuaState, int a_TableIdx, int a_NumAllowedNestingLevels); + + /** Copies a single value from the specified stack index of the source Lua state to the top of this Lua state's stack. + a_NumAllowedNestingLevels specifies how many table nesting levels are allowed, copying fails if there's a deeper table. + Returns true if the value was copied, false on failure. */ + bool CopySingleValueFrom(cLuaState & a_SrcLuaState, int a_StackIdx, int a_NumAllowedNestingLevels); /** Reads the value at the specified stack position as a string and sets it to a_String. */ void ToString(int a_StackPos, AString & a_String); -- cgit v1.2.3