diff options
author | Nat Goodspeed <nat@lindenlab.com> | 2024-02-07 12:50:26 -0500 |
---|---|---|
committer | Nat Goodspeed <nat@lindenlab.com> | 2024-02-07 12:50:26 -0500 |
commit | 5b0404961e35ecca46148b90f2c27aed1bad607f (patch) | |
tree | 2ea3cd1c19e719536a57ff96aecb5e0347eb19c9 | |
parent | f664c2ea26fb63f162f3d988b6d00f1483be5d45 (diff) |
Add machinery to capture result of running a Lua script or snippet.
Add LuaState::expr() that evaluates a Lua snippet and reports back any result
(or error) left on the stack.
Add LLLUAmanager::runScriptFile() and runScriptLine() overloads that accept a
callback with an (int count, LLSD result) signature. The count disambiguates
(error, no result, one result, array of results). Also add overloads that accept
an existing LuaState instance. Also add waitScriptFile() and waitScriptLine()
methods that pause the calling coroutine until the Lua script completes, and
return its results.
Instead of giving LuaState a description to use for all subsequent checkLua()
calls, remove description from its constructor and data members. Move to
expr() and checkLua() parameters: we want a description specific to each
operation, rather than for the LuaState as a whole. This prepares for
persistent LuaState instances.
For now, the existing script_finished_fn semantics remain: the callback will
be called only when the LuaState is destroyed. This may need to change as we
migrate towards longer-lasting LuaState instances.
Make lua_function(name) macro append suffixes to the name for both the
LuaFunction subclass declaration and the instance declaration. This allows
publishing a lua_function() name such as sleep(), which already has a
different C++ declaration.
Move the Lua sleep() entry point to a standalone lua_function(sleep), instead
of a lambda in the body of runScriptFile().
-rw-r--r-- | indra/llcommon/lua_function.cpp | 37 | ||||
-rw-r--r-- | indra/llcommon/lua_function.h | 30 | ||||
-rw-r--r-- | indra/newview/llluamanager.cpp | 93 | ||||
-rw-r--r-- | indra/newview/llluamanager.h | 30 |
4 files changed, 156 insertions, 34 deletions
diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index f6db4eafe1..5466e0058e 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -407,8 +407,7 @@ void lua_pushllsd(lua_State* L, const LLSD& data) } } -LuaState::LuaState(const std::string_view& desc, script_finished_fn cb): - mDesc(desc), +LuaState::LuaState(script_finished_fn cb): mCallback(cb), mState(luaL_newstate()) { @@ -450,19 +449,49 @@ LuaState::~LuaState() } } -bool LuaState::checkLua(int r) +bool LuaState::checkLua(const std::string& desc, int r) { if (r != LUA_OK) { mError = lua_tostring(mState, -1); lua_pop(mState, 1); - LL_WARNS() << mDesc << ": " << mError << LL_ENDL; + LL_WARNS() << desc << ": " << mError << LL_ENDL; return false; } return true; } +std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string& text) +{ + if (! checkLua(desc, lluau::dostring(mState, desc, text))) + return { -1, mError }; + + // here we believe there was no error -- did the Lua fragment leave + // anything on the stack? + std::pair<int, LLSD> result{ lua_gettop(mState), {} }; + if (! result.first) + return result; + + // aha, at least one entry on the stack! + if (result.first == 1) + { + result.second = lua_tollsd(mState, 1); + // pop the result we claimed + lua_settop(mState, 0); + return result; + } + + // multiple entries on the stack + for (int index = 1; index <= result.first; ++index) + { + result.second.append(lua_tollsd(mState, index)); + } + // pop everything + lua_settop(mState, 0); + return result; +} + LuaPopper::~LuaPopper() { diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index e2bd58e80a..ea9f2ebdf8 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -17,6 +17,7 @@ #include "luau/luaconf.h" #include "luau/lualib.h" #include "stringize.h" +#include <utility> // std::pair #define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n))) #define lua_rawlen lua_objlen @@ -55,19 +56,28 @@ class LuaState public: typedef std::function<void(std::string msg)> script_finished_fn; - LuaState(const std::string_view& desc, script_finished_fn cb); + LuaState(script_finished_fn cb={}); LuaState(const LuaState&) = delete; LuaState& operator=(const LuaState&) = delete; ~LuaState(); - bool checkLua(int r); + bool checkLua(const std::string& desc, int r); + + // expr() is for when we want to capture any results left on the stack + // by a Lua expression, possibly including multiple return values. + // int < 0 means error, and LLSD::asString() is the error message. + // int == 0 with LLSD::isUndefined() means the Lua expression returned no + // results. + // int == 1 means the Lua expression returned one result. + // int > 1 with LLSD::isArray() means the Lua expression returned + // multiple results, represented as the entries of the array. + std::pair<int, LLSD> expr(const std::string& desc, const std::string& text); operator lua_State*() const { return mState; } private: - std::string mDesc; script_finished_fn mCallback; lua_State* mState; std::string mError; @@ -129,13 +139,13 @@ private: * call() method definition header, to be followed by a method body enclosed * in curly braces as usual. */ -#define lua_function(name) \ -static struct name##_ : public LuaFunction \ -{ \ - name##_(): LuaFunction(#name, &call) {} \ - static int call(lua_State* L); \ -} name; \ -int name##_::call(lua_State* L) +#define lua_function(name) \ +static struct name##_luadecl : public LuaFunction \ +{ \ + name##_luadecl(): LuaFunction(#name, &call) {} \ + static int call(lua_State* L); \ +} name##_luadef; \ +int name##_luadecl::call(lua_State* L) // { // ... supply method body here, referencing 'L' ... // } diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 596d8cab56..44c7a6ba3b 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -28,6 +28,7 @@ #include "llviewerprecompiledheaders.h" #include "llluamanager.h" +#include "llcoros.h" #include "llerror.h" #include "lleventcoro.h" #include "lua_function.h" @@ -55,6 +56,14 @@ extern LLUIListener sUIListener; #include <string_view> #include <vector> +lua_function(sleep) +{ + F32 seconds = lua_tonumber(L, -1); + lua_pop(L, 1); + llcoro::suspendUntilTimeout(seconds); + return 0; +}; + /* // This function consumes ALL Lua stack arguments and returns concatenated // message string @@ -262,21 +271,34 @@ lua_function(await_event) void LLLUAmanager::runScriptFile(const std::string& filename, script_finished_fn cb) { - std::string desc{ stringize("runScriptFile('", filename, "')") }; - LLCoros::instance().launch(desc, [desc, filename, cb]() - { - LuaState L(desc, cb); + // A script_finished_fn is used to initialize the LuaState. + // It will be called when the LuaState is destroyed. + LuaState L(cb); + runScriptFile(L, filename); +} - auto LUA_sleep_func = [](lua_State *L) - { - F32 seconds = lua_tonumber(L, -1); - lua_pop(L, 1); - llcoro::suspendUntilTimeout(seconds); - return 0; - }; +void LLLUAmanager::runScriptFile(const std::string& filename, script_result_fn cb) +{ + LuaState L; + // A script_result_fn will be called when LuaState::expr() completes. + runScriptFile(L, filename, cb); +} - lua_register(L, "sleep", LUA_sleep_func); +std::pair<int, LLSD> LLLUAmanager::waitScriptFile(LuaState& L, const std::string& filename) +{ + LLCoros::Promise<std::pair<int, LLSD>> promise; + auto future{ LLCoros::getFuture(promise) }; + runScriptFile(L, filename, + [&promise](int count, LLSD result) + { promise.set_value({ count, result }); }); + return future.get(); +} +void LLLUAmanager::runScriptFile(LuaState& L, const std::string& filename, script_result_fn cb) +{ + std::string desc{ stringize("runScriptFile('", filename, "')") }; + LLCoros::instance().launch(desc, [&L, desc, filename, cb]() + { llifstream in_file; in_file.open(filename.c_str()); @@ -284,17 +306,51 @@ void LLLUAmanager::runScriptFile(const std::string& filename, script_finished_fn { std::string text{std::istreambuf_iterator<char>(in_file), std::istreambuf_iterator<char>()}; - L.checkLua(lluau::dostring(L, desc, text)); + auto [count, result] = L.expr(desc, text); + if (cb) + { + cb(count, result); + } } else { - LL_WARNS("Lua") << "unable to open script file '" << filename << "'" << LL_ENDL; + auto msg{ stringize("unable to open script file '", filename, "'") }; + LL_WARNS("Lua") << msg << LL_ENDL; + if (cb) + { + cb(-1, msg); + } } }); } void LLLUAmanager::runScriptLine(const std::string& cmd, script_finished_fn cb) { + // A script_finished_fn is used to initialize the LuaState. + // It will be called when the LuaState is destroyed. + LuaState L(cb); + runScriptLine(L, cmd); +} + +void LLLUAmanager::runScriptLine(const std::string& cmd, script_result_fn cb) +{ + LuaState L; + // A script_result_fn will be called when LuaState::expr() completes. + runScriptLine(L, cmd, cb); +} + +std::pair<int, LLSD> LLLUAmanager::waitScriptLine(LuaState& L, const std::string& cmd) +{ + LLCoros::Promise<std::pair<int, LLSD>> promise; + auto future{ LLCoros::getFuture(promise) }; + runScriptLine(L, cmd, + [&promise](int count, LLSD result) + { promise.set_value({ count, result }); }); + return future.get(); +} + +void LLLUAmanager::runScriptLine(LuaState& L, const std::string& cmd, script_result_fn cb) +{ // find a suitable abbreviation for the cmd string std::string_view shortcmd{ cmd }; const size_t shortlen = 40; @@ -305,10 +361,13 @@ void LLLUAmanager::runScriptLine(const std::string& cmd, script_finished_fn cb) shortcmd = stringize(shortcmd.substr(0, shortlen), "..."); std::string desc{ stringize("runScriptLine('", shortcmd, "')") }; - LLCoros::instance().launch(desc, [desc, cmd, cb]() + LLCoros::instance().launch(desc, [&L, desc, cmd, cb]() { - LuaState L(desc, cb); - L.checkLua(lluau::dostring(L, desc, cmd)); + auto [count, result] = L.expr(desc, cmd); + if (cb) + { + cb(count, result); + } }); } diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h index 08d9876ce2..bed12e358a 100644 --- a/indra/newview/llluamanager.h +++ b/indra/newview/llluamanager.h @@ -27,19 +27,43 @@ #ifndef LL_LLLUAMANAGER_H #define LL_LLLUAMANAGER_H +#include "llsd.h" #include <functional> #include <string> +#include <utility> // std::pair + +class LuaState; class LLLUAmanager { public: + // Pass a callback with this signature to obtain the error message, if + // any, from running a script or source string. Empty msg means success. typedef std::function<void(std::string msg)> script_finished_fn; + // Pass a callback with this signature to obtain the result, if any, of + // running a script or source string. + // count < 0 means error, and result.asString() is the error message. + // count == 0 with result.isUndefined() means the script returned no results. + // count == 1 means the script returned one result. + // count > 1 with result.isArray() means the script returned multiple + // results, represented as the entries of the result array. + typedef std::function<void(int count, const LLSD& result)> script_result_fn; + + static void runScriptFile(const std::string &filename, script_finished_fn cb = {}); + static void runScriptFile(const std::string &filename, script_result_fn cb); + static void runScriptFile(LuaState& L, const std::string &filename, script_result_fn cb = {}); + // Run a Lua script file, and pause the calling coroutine until it completes. + // The return value is the (count, result) pair described above. + static std::pair<int, LLSD> waitScriptFile(LuaState& L, const std::string& filename); - static void runScriptFile(const std::string &filename, script_finished_fn cb = script_finished_fn()); - static void runScriptLine(const std::string &cmd, script_finished_fn cb = script_finished_fn()); + static void runScriptLine(const std::string &cmd, script_finished_fn cb = {}); + static void runScriptLine(const std::string &cmd, script_result_fn cb); + static void runScriptLine(LuaState& L, const std::string &cmd, script_result_fn cb = {}); + // Run a Lua expression string, and pause the calling coroutine until it completes. + // The return value is the (count, result) pair described above. + static std::pair<int, LLSD> waitScriptLine(LuaState& L, const std::string& cmd); static void runScriptOnLogin(); }; - #endif |