summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
authornat-goodspeed <nat@lindenlab.com>2024-03-25 12:35:48 -0400
committerGitHub <noreply@github.com>2024-03-25 12:35:48 -0400
commit2eb6901c7c9ae87a588d99399e4b41640e4c4881 (patch)
tree6621d8e565119a131af1edc9397039f7af6be8b2 /indra/llcommon
parent7bf84bdcbf13084ff3b94590e4061b4a6708b4dc (diff)
parentfd8c5fced1ee62e08c55adf92fb9c8d0e52d313a (diff)
Merge pull request #1038 from secondlife/lua-fiber
Add fiber.lua, which permits calling leap.request() even from Lua's main thread.
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/llevents.cpp13
-rw-r--r--indra/llcommon/llevents.h28
-rw-r--r--indra/llcommon/lua_function.cpp128
3 files changed, 115 insertions, 54 deletions
diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp
index 01bba7a620..667e047fd3 100644
--- a/indra/llcommon/llevents.cpp
+++ b/indra/llcommon/llevents.cpp
@@ -726,7 +726,7 @@ void LLReqID::stamp(LLSD& response) const
response["reqid"] = mReqid;
}
-bool sendReply(const LLSD& reply, const LLSD& request, const std::string& replyKey)
+bool sendReply(LLSD reply, const LLSD& request, const std::string& replyKey)
{
// If the original request has no value for replyKey, it's pointless to
// construct or send a reply event: on which LLEventPump should we send
@@ -739,13 +739,10 @@ bool sendReply(const LLSD& reply, const LLSD& request, const std::string& replyK
// Here the request definitely contains replyKey; reasonable to proceed.
- // Copy 'reply' to modify it.
- LLSD newreply(reply);
// Get the ["reqid"] element from request
LLReqID reqID(request);
- // and copy it to 'newreply'.
- reqID.stamp(newreply);
- // Send reply on LLEventPump named in request[replyKey]. Don't forget to
- // send the modified 'newreply' instead of the original 'reply'.
- return LLEventPumps::instance().obtain(request[replyKey]).post(newreply);
+ // and copy it to 'reply'.
+ reqID.stamp(reply);
+ // Send reply on LLEventPump named in request[replyKey].
+ return LLEventPumps::instance().obtain(request[replyKey]).post(reply);
}
diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h
index c1dbf4392f..77a405871d 100644
--- a/indra/llcommon/llevents.h
+++ b/indra/llcommon/llevents.h
@@ -151,6 +151,8 @@ typedef boost::signals2::signal<bool(const LLSD&), LLStopWhenHandled, float> LL
/// Methods that forward listeners (e.g. constructed with
/// <tt>boost::bind()</tt>) should accept (const LLEventListener&)
typedef LLStandardSignal::slot_type LLEventListener;
+/// Accept a void listener too
+typedef std::function<void(const LLSD&)> LLVoidListener;
/// Result of registering a listener, supports <tt>connected()</tt>,
/// <tt>disconnect()</tt> and <tt>blocked()</tt>
typedef boost::signals2::connection LLBoundListener;
@@ -688,6 +690,30 @@ private:
};
/*****************************************************************************
+* LLNamedListener
+*****************************************************************************/
+/**
+ * LLNamedListener bundles a concrete LLEventPump subclass with a specific
+ * listener function, with an LLTempBoundListener to ensure that it's
+ * disconnected before destruction.
+ */
+template <class PUMP=LLEventStream>
+class LL_COMMON_API LLNamedListener: PUMP
+{
+ using pump_t = PUMP;
+public:
+ template <typename LISTENER>
+ LLNamedListener(const std::string& name, LISTENER&& listener):
+ pump_t(name, false), // don't tweak the name
+ mConn(pump_t::listen("func", std::forward<LISTENER>(listener)))
+ {}
+
+private:
+ LLTempBoundListener mConn;
+};
+using LLStreamListener = LLNamedListener<>;
+
+/*****************************************************************************
* LLReqID
*****************************************************************************/
/**
@@ -779,7 +805,7 @@ private:
* Before sending the reply event, sendReply() copies the ["reqid"] item from
* the request to the reply.
*/
-LL_COMMON_API bool sendReply(const LLSD& reply, const LLSD& request,
+LL_COMMON_API bool sendReply(LLSD reply, const LLSD& request,
const std::string& replyKey="reply");
#endif /* ! defined(LL_LLEVENTS_H) */
diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp
index b5de5099ba..441e17dafd 100644
--- a/indra/llcommon/lua_function.cpp
+++ b/indra/llcommon/lua_function.cpp
@@ -17,6 +17,7 @@
// std headers
#include <algorithm>
#include <exception>
+#include <filesystem>
#include <iomanip> // std::quoted
#include <map>
#include <memory> // std::unique_ptr
@@ -97,8 +98,6 @@ void lua_pushstdstring(lua_State* L, const std::string& str)
// reached by that block raises a Lua error.
LLSD lua_tollsd(lua_State* L, int index)
{
- LL_DEBUGS("Lua") << "lua_tollsd(" << index << ") of " << lua_gettop(L) << " stack entries: "
- << lua_what(L, index) << LL_ENDL;
switch (lua_type(L, index))
{
case LUA_TNONE:
@@ -200,15 +199,12 @@ LLSD lua_tollsd(lua_State* L, int index)
// we do, below: lua_tollsd(L, -1). If 'index' is -1, then when we
// push nil, what we find at index -1 is nil, not the table!
index = lua_absindex(L, index);
- LL_DEBUGS("Lua") << "checking for empty table" << LL_ENDL;
lua_pushnil(L); // first key
- LL_DEBUGS("Lua") << lua_stack(L) << LL_ENDL;
if (! lua_next(L, index))
{
// it's a table, but the table is empty -- no idea if it should be
// modeled as empty array or empty map -- return isUndefined(),
// which can be consumed as either
- LL_DEBUGS("Lua") << "empty table" << LL_ENDL;
return {};
}
// key is at stack index -2, value at index -1
@@ -217,8 +213,6 @@ LLSD lua_tollsd(lua_State* L, int index)
LuaPopper popper(L, 2);
// Remember the type of the first key
auto firstkeytype{ lua_type(L, -2) };
- LL_DEBUGS("Lua") << "table not empty, first key type " << lua_typename(L, firstkeytype)
- << LL_ENDL;
switch (firstkeytype)
{
case LUA_TNUMBER:
@@ -296,7 +290,6 @@ LLSD lua_tollsd(lua_State* L, int index)
// crazy key territory.
return lluau::error(L, "Gaps in Lua table too large for conversion to LLSD array");
}
- LL_DEBUGS("Lua") << "collected " << keys.size() << " keys, max " << highkey << LL_ENDL;
// right away expand the result array to the size we'll need
LLSD result{ LLSD::emptyArray() };
result[highkey - 1] = LLSD();
@@ -307,7 +300,6 @@ LLSD lua_tollsd(lua_State* L, int index)
// key at stack index -2, value at index -1
// We've already validated lua_tointegerx() for each key.
auto key{ lua_tointeger(L, -2) };
- LL_DEBUGS("Lua") << "key " << key << ':' << LL_ENDL;
// Don't forget to subtract 1 from Lua key for LLSD subscript!
result[LLSD::Integer(key) - 1] = lua_tollsd(L, -1);
// remove value, keep key for next iteration
@@ -330,7 +322,6 @@ LLSD lua_tollsd(lua_State* L, int index)
}
auto key{ lua_tostdstring(L, -2) };
- LL_DEBUGS("Lua") << "map key " << std::quoted(key) << ':' << LL_ENDL;
result[key] = lua_tollsd(L, -1);
// remove value, keep key for next iteration
lua_pop(L, 1);
@@ -493,53 +484,100 @@ std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string&
// 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)
+ if (result.first)
{
- // Don't forget that lua_tollsd() can throw Lua errors.
- try
+ // aha, at least one entry on the stack!
+ if (result.first == 1)
{
- result.second = lua_tollsd(mState, 1);
+ // Don't forget that lua_tollsd() can throw Lua errors.
+ try
+ {
+ result.second = lua_tollsd(mState, 1);
+ }
+ catch (const std::exception& error)
+ {
+ // lua_tollsd() is designed to be called from a lua_function(),
+ // that is, from a C++ function called by Lua. In case of error,
+ // it throws a Lua error to be caught by the Lua runtime. expr()
+ // is a peculiar use case in which our C++ code is calling
+ // lua_tollsd() after return from the Lua runtime. We must catch
+ // the exception thrown for a Lua error, else it will propagate
+ // out to the main coroutine and terminate the viewer -- but since
+ // we instead of the Lua runtime catch it, our lua_State retains
+ // its internal error status. Any subsequent lua_pcall() calls
+ // with this lua_State will report error regardless of whether the
+ // chunk runs successfully. Get a new lua_State().
+ initLuaState();
+ return { -1, stringize(LLError::Log::classname(error), ": ", error.what()) };
+ }
}
- catch (const std::exception& error)
+ else
{
- // lua_tollsd() is designed to be called from a lua_function(),
- // that is, from a C++ function called by Lua. In case of error,
- // it throws a Lua error to be caught by the Lua runtime. expr()
- // is a peculiar use case in which our C++ code is calling
- // lua_tollsd() after return from the Lua runtime. We must catch
- // the exception thrown for a Lua error, else it will propagate
- // out to the main coroutine and terminate the viewer -- but since
- // we instead of the Lua runtime catch it, our lua_State retains
- // its internal error status. Any subsequent lua_pcall() calls
- // with this lua_State will report error regardless of whether the
- // chunk runs successfully. Get a new lua_State().
- initLuaState();
- return { -1, stringize(LLError::Log::classname(error), ": ", error.what()) };
+ // multiple entries on the stack
+ try
+ {
+ for (int index = 1; index <= result.first; ++index)
+ {
+ result.second.append(lua_tollsd(mState, index));
+ }
+ }
+ catch (const std::exception& error)
+ {
+ // see above comments regarding lua_State's error status
+ initLuaState();
+ return { -1, stringize(LLError::Log::classname(error), ": ", error.what()) };
+ }
}
- // pop the result we claimed
- lua_settop(mState, 0);
- return result;
}
+ // pop everything
+ lua_settop(mState, 0);
- // multiple entries on the stack
- try
- {
- for (int index = 1; index <= result.first; ++index)
+ // If we ran a script that loaded the fiber module, finish up with a call
+ // to fiber.run(). That allows a script to kick off some number of fibers,
+ // do some work on the main thread and then fall off the end of the script
+ // without explicitly appending a call to fiber.run(). run() ensures the
+ // rest of the fibers run to completion (or error).
+ luaL_checkstack(mState, 4, nullptr);
+ // Push _MODULES table on stack
+ luaL_findtable(mState, LUA_REGISTRYINDEX, "_MODULES", 1);
+ int index = lua_gettop(mState);
+ bool found = false;
+ // Did this chunk already require('fiber')? To find out, we must search
+ // the _MODULES table, because our require() implementation uses the
+ // pathname of the module file as the key. Push nil key to start.
+ lua_pushnil(mState);
+ while (lua_next(mState, index) != 0)
+ {
+ // key is at index -2, value at index -1
+ // "While traversing a table, do not call lua_tolstring directly on a
+ // key, unless you know that the key is actually a string. Recall that
+ // lua_tolstring changes the value at the given index; this confuses
+ // the next call to lua_next."
+ // https://www.lua.org/manual/5.1/manual.html#lua_next
+ if (lua_type(mState, -2) == LUA_TSTRING &&
+ std::filesystem::path(lua_tostdstring(mState, -2)).stem() == "fiber")
{
- result.second.append(lua_tollsd(mState, index));
+ found = true;
+ break;
}
+ // pop value so key is at top for lua_next()
+ lua_pop(mState, 1);
}
- catch (const std::exception& error)
+ if (found)
{
- // see above comments regarding lua_State's error status
- initLuaState();
- return { -1, stringize(LLError::Log::classname(error), ": ", error.what()) };
+ // okay, index -1 is a table loaded from a file 'fiber.xxx' --
+ // does it have a function named 'run'?
+ auto run_type{ lua_getfield(mState, -1, "run") };
+ if (run_type == LUA_TFUNCTION)
+ {
+ // there's a fiber.run() function sitting on the top of the stack
+ // -- call it with no arguments, discarding anything it returns
+ LL_DEBUGS("Lua") << "Calling fiber.run()" << LL_ENDL;
+ if (! checkLua(desc, lua_pcall(mState, 0, 0, 0)))
+ return { -1, mError };
+ }
}
- // pop everything
+ // pop everything again
lua_settop(mState, 0);
return result;
}