From 7bdb687e4ae5747b0a05f717c3ee5bee51cb1021 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 10 Feb 2014 20:09:06 +0400 Subject: [PATCH] Support calling a lua function via a protobuf request. Previously the only way to call lua code was to call scripts and parse their output to the stream, which is cumbersome. --- NEWS | 3 ++ library/RemoteTools.cpp | 86 ++++++++++++++++++++++++++++++++ library/dfhack-run.cpp | 47 +++++++++++++++-- library/include/RemoteTools.h | 8 +++ library/proto/CoreProtocol.proto | 7 +++ 5 files changed, 146 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index a672f2766..854c9353e 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,8 @@ DFHack future + Internals: + - support for calling a lua function via a protobuf request (demonstrated by dfhack-run --lua). + New commands: - move the 'grow', 'extirpate' and 'immolate' commands as 'plant' subcommands - 'plant create' - spawn a new shrub under the cursor diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 718d1b181..17f5258e1 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -55,6 +55,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "modules/Units.h" #include "modules/World.h" +#include "LuaTools.h" + #include "DataDefs.h" #include "df/ui.h" #include "df/ui_advmode.h" @@ -656,6 +658,8 @@ CoreService::CoreService() { addMethod("CoreSuspend", &CoreService::CoreSuspend, SF_DONT_SUSPEND); addMethod("CoreResume", &CoreService::CoreResume, SF_DONT_SUSPEND); + addMethod("RunLua", &CoreService::RunLua); + // Functions: addFunction("GetVersion", GetVersion, SF_DONT_SUSPEND); addFunction("GetDFVersion", GetDFVersion, SF_DONT_SUSPEND); @@ -730,3 +734,85 @@ command_result CoreService::CoreResume(color_ostream &stream, const EmptyMessage cnt->set_value(--suspend_depth); return CR_OK; } + +namespace { + struct LuaFunctionData { + command_result rv; + const dfproto::CoreRunLuaRequest *in; + StringListMessage *out; + }; +} + +command_result CoreService::RunLua(color_ostream &stream, + const dfproto::CoreRunLuaRequest *in, + StringListMessage *out) +{ + auto L = Lua::Core::State; + LuaFunctionData data = { CR_FAILURE, in, out }; + + lua_pushcfunction(L, doRunLuaFunction); + lua_pushlightuserdata(L, &data); + + if (!Lua::Core::SafeCall(stream, 1, 0)) + return CR_FAILURE; + + return data.rv; +} + +int CoreService::doRunLuaFunction(lua_State *L) +{ + color_ostream &out = *Lua::GetOutput(L); + auto &args = *(LuaFunctionData*)lua_touserdata(L, 1); + + // Verify module name + std::string module = args.in->module(); + size_t len = module.size(); + + bool valid = false; + + if (len > 4) + { + if (module.substr(0,4) == "rpc.") + valid = true; + else if ((module[len-4] == '.' || module[len-4] == '-') && module.substr(len-3) != "rpc") + valid = true; + } + + if (!valid) + { + args.rv = CR_WRONG_USAGE; + out.printerr("Only modules named rpc.* or *.rpc or *-rpc may be called.\n"); + return 0; + } + + // Prepare function and arguments + lua_settop(L, 0); + + if (!Lua::PushModulePublic(out, L, module.c_str(), args.in->function().c_str()) + || lua_isnil(L, 1)) + { + args.rv = CR_NOT_FOUND; + return 0; + } + + luaL_checkstack(L, args.in->arguments_size(), "too many arguments"); + + for (int i = 0; i < args.in->arguments_size(); i++) + lua_pushstring(L, args.in->arguments(i).c_str()); + + // Call + lua_call(L, args.in->arguments_size(), LUA_MULTRET); + + // Store results + int nresults = lua_gettop(L); + + for (int i = 1; i <= nresults; i++) + { + size_t len; + const char *data = lua_tolstring(L, i, &len); + args.out->add_value(std::string(data, len)); + } + + args.rv = CR_OK; + return 0; +} diff --git a/library/dfhack-run.cpp b/library/dfhack-run.cpp index 69188f2d6..abe34f34d 100644 --- a/library/dfhack-run.cpp +++ b/library/dfhack-run.cpp @@ -72,12 +72,49 @@ int main (int argc, char *argv[]) if (!client.connect()) return 2; - // Call the command - std::vector args; - for (int i = 2; i < argc; i++) - args.push_back(argv[i]); + command_result rv; - command_result rv = client.run_command(argv[1], args); + if (strcmp(argv[1], "--lua") == 0) + { + if (argc <= 3) + { + fprintf(stderr, "Usage: dfhack-run --lua [args...]\n"); + return 2; + } + + RemoteFunction run_call; + + if (!run_call.bind(&client, "RunLua")) + { + fprintf(stderr, "No RunLua protocol function found."); + return 3; + } + + run_call.in()->set_module(argv[2]); + run_call.in()->set_function(argv[3]); + for (int i = 4; i < argc; i++) + run_call.in()->add_arguments(argv[i]); + + rv = run_call(); + + out.flush(); + + if (rv == CR_OK) + { + for (int i = 0; i < run_call.out()->value_size(); i++) + printf("%s%s", (i>0?"\t":""), run_call.out()->value(i).c_str()); + printf("\n"); + } + } + else + { + // Call the command + std::vector args; + for (int i = 2; i < argc; i++) + args.push_back(argv[i]); + + rv = client.run_command(argv[1], args); + } out.flush(); diff --git a/library/include/RemoteTools.h b/library/include/RemoteTools.h index e70cefc00..ead1c0aa1 100644 --- a/library/include/RemoteTools.h +++ b/library/include/RemoteTools.h @@ -48,6 +48,8 @@ namespace DFHack DFHACK_EXPORT void strVectorToRepeatedField(RepeatedPtrField *pf, const std::vector &vec); + using dfproto::StringListMessage; + /** * Represent bitfield bits as a repeated string field. */ @@ -131,6 +133,8 @@ namespace DFHack class CoreService : public RPCService { int suspend_depth; + + static int doRunLuaFunction(lua_State *L); public: CoreService(); ~CoreService(); @@ -144,5 +148,9 @@ namespace DFHack // For batching command_result CoreSuspend(color_ostream &stream, const EmptyMessage*, IntMessage *cnt); command_result CoreResume(color_ostream &stream, const EmptyMessage*, IntMessage *cnt); + + command_result RunLua(color_ostream &stream, + const dfproto::CoreRunLuaRequest *in, + StringListMessage *out); }; } diff --git a/library/proto/CoreProtocol.proto b/library/proto/CoreProtocol.proto index 92d7c48d9..e53078875 100644 --- a/library/proto/CoreProtocol.proto +++ b/library/proto/CoreProtocol.proto @@ -81,3 +81,10 @@ message CoreRunCommandRequest { // RPC CoreSuspend : EmptyMessage -> IntMessage // RPC CoreResume : EmptyMessage -> IntMessage + +// RPC RunLua : CoreRunLuaRequest -> StringListMessage +message CoreRunLuaRequest { + required string module = 1; + required string function = 2; + repeated string arguments = 3; +}