summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--autobuild.xml20
-rw-r--r--indra/llcommon/llstring.h39
-rw-r--r--indra/llcommon/lua_function.cpp226
-rw-r--r--indra/llcommon/lua_function.h22
-rw-r--r--indra/llcommon/stringize.h6
-rw-r--r--indra/llui/lltexteditor.cpp3
-rw-r--r--indra/llui/lltexteditor.h14
-rw-r--r--indra/newview/llfloaterluadebug.cpp8
-rw-r--r--indra/newview/llluamanager.cpp29
9 files changed, 295 insertions, 72 deletions
diff --git a/autobuild.xml b/autobuild.xml
index 708bc1aeeb..1415ef823b 100644
--- a/autobuild.xml
+++ b/autobuild.xml
@@ -1704,15 +1704,29 @@
<key>name</key>
<string>windows64</string>
</map>
+ <key>linux64</key>
+ <map>
+ <key>archive</key>
+ <map>
+ <key>hash</key>
+ <string>549516ada483ddf276183f66b0a7c3d01e4ef1aa</string>
+ <key>hash_algorithm</key>
+ <string>sha1</string>
+ <key>url</key>
+ <string>https://github.com/secondlife/3p-luau/releases/download/v0.609-9567db9/luau-0.609-9567db9-linux64-9567db9.tar.zst</string>
+ </map>
+ <key>name</key>
+ <string>linux64</string>
+ </map>
</map>
<key>license</key>
<string>MIT</string>
<key>license_file</key>
- <string>LICENSES/lua.txt</string>
+ <string>LICENSES/luau.txt</string>
<key>copyright</key>
<string>Copyright (c) 2019-2023 Roblox Corporation, Copyright (c) 1994–2019 Lua.org, PUC-Rio.</string>
<key>version</key>
- <string>0.609</string>
+ <string>0.609-9567db9</string>
<key>name</key>
<string>luau</string>
<key>description</key>
@@ -2519,8 +2533,6 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>https://bitbucket.org/lindenlab/3p-tracy</string>
<key>source_type</key>
<string>git</string>
- <key>version</key>
- <string>v0.8.1.578241</string>
</map>
<key>tut</key>
<map>
diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h
index b43093fbfc..dbd60bc9c7 100644
--- a/indra/llcommon/llstring.h
+++ b/indra/llcommon/llstring.h
@@ -516,9 +516,36 @@ struct ll_convert_impl
TO operator()(const FROM& in) const;
};
-// Use a function template to get the nice ll_convert<TO>(from_value) API.
+/**
+ * somefunction(ll_convert(data))
+ * target = ll_convert(data)
+ * totype otherfunc(const fromtype& data)
+ * {
+ * // ...
+ * return ll_convert(data);
+ * }
+ * all infer both the FROM type and the TO type.
+ */
+template <typename FROM>
+class ll_convert
+{
+private:
+ const FROM& mRef;
+
+public:
+ ll_convert(const FROM& ref): mRef(ref) {}
+
+ template <typename TO>
+ inline operator TO() const
+ {
+ return ll_convert_impl<TO, FROM>()(mRef);
+ }
+};
+
+// When the TO type must be explicit, use a function template to get
+// ll_convert_to<TO>(from_value) API.
template<typename TO, typename FROM>
-TO ll_convert(const FROM& in)
+TO ll_convert_to(const FROM& in)
{
return ll_convert_impl<TO, FROM>()(in);
}
@@ -574,8 +601,8 @@ inline size_t ll_convert_length<char> (const char* zstr) { return std::strl
// and longname(const string&, len) so calls written pre-ll_convert() will
// work. Most of these overloads will be unified once we turn on C++17 and can
// use std::string_view.
-// It also uses aliasmacro to ensure that both ll_convert<OUTSTR>(const char*)
-// and ll_convert<OUTSTR>(const string&) will work.
+// It also uses aliasmacro to ensure that both ll_convert(const char*)
+// and ll_convert(const string&) will work.
#define ll_convert_forms(aliasmacro, OUTSTR, INSTR, longname) \
LL_COMMON_API OUTSTR longname(const INSTR::value_type* in, size_t len); \
inline auto longname(const INSTR& in, size_t len) \
@@ -807,7 +834,7 @@ LL_COMMON_API std::string ll_convert_string_to_utf8_string(const std::string& in
template<typename STRING>
STRING windows_message(unsigned long error)
{
- return ll_convert<STRING>(windows_message<std::wstring>(error));
+ return ll_convert(windows_message<std::wstring>(error));
}
/// There's only one real implementation
@@ -1780,7 +1807,7 @@ auto LLStringUtilBase<T>::getoptenv(const std::string& key) -> boost::optional<s
if (found)
{
// return populated boost::optional
- return { ll_convert<string_type>(*found) };
+ return { ll_convert_to<string_type>(*found) };
}
else
{
diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp
index 07e0c1fac2..13210fe5a6 100644
--- a/indra/llcommon/lua_function.cpp
+++ b/indra/llcommon/lua_function.cpp
@@ -21,10 +21,14 @@
// external library headers
// other Linden headers
#include "hexdump.h"
+#include "lleventcoro.h"
#include "llsd.h"
#include "llsdutil.h"
#include "lualistener.h"
+/*****************************************************************************
+* luau namespace
+*****************************************************************************/
namespace
{
// can't specify free function free() as a unique_ptr deleter
@@ -53,6 +57,9 @@ int lluau::dostring(lua_State* L, const std::string& desc, const std::string& te
return lua_pcall(L, 0, LUA_MULTRET, 0);
}
+/*****************************************************************************
+* Lua <=> C++ conversions
+*****************************************************************************/
std::string lua_tostdstring(lua_State* L, int index)
{
size_t len;
@@ -414,6 +421,9 @@ void lua_pushllsd(lua_State* L, const LLSD& data)
}
}
+/*****************************************************************************
+* LuaState class
+*****************************************************************************/
LuaState::LuaState(script_finished_fn cb):
mCallback(cb),
mState(luaL_newstate())
@@ -426,25 +436,15 @@ LuaState::LuaState(script_finished_fn cb):
LuaState::~LuaState()
{
- // Did somebody call listen_events() on this LuaState?
+ // Did somebody call obtainListener() on this LuaState?
// That is, is there a LuaListener key in its registry?
- auto keytype{ lua_getfield(mState, LUA_REGISTRYINDEX, "event.listener") };
- if (keytype == LUA_TNUMBER)
+ auto listener{ getListener() };
+ if (listener)
{
- // We do have a LuaListener. Retrieve it.
- int isint;
- auto listener{ LuaListener::getInstance(lua_tointegerx(mState, -1, &isint)) };
- // pop the int "event.listener" key
- lua_pop(mState, 1);
// if we got a LuaListener instance, destroy it
- // (if (! isint), lua_tointegerx() returned 0, but key 0 might
- // validly designate someone ELSE's LuaListener)
- if (isint && listener)
- {
- auto lptr{ listener.get() };
- listener.reset();
- delete lptr;
- }
+ auto lptr{ listener.get() };
+ listener.reset();
+ delete lptr;
}
lua_close(mState);
@@ -499,7 +499,48 @@ std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string&
return result;
}
+LuaListener::ptr_t LuaState::getListener(lua_State* L)
+{
+ // have to use one more stack slot
+ luaL_checkstack(L, 1, nullptr);
+ LuaListener::ptr_t listener;
+ // Does this lua_State already have a LuaListener stored in the registry?
+ auto keytype{ lua_getfield(L, LUA_REGISTRYINDEX, "event.listener") };
+ llassert(keytype == LUA_TNIL || keytype == LUA_TNUMBER);
+ if (keytype == LUA_TNUMBER)
+ {
+ // We do already have a LuaListener. Retrieve it.
+ int isint;
+ listener = LuaListener::getInstance(lua_tointegerx(L, -1, &isint));
+ // Nobody should have destroyed this LuaListener instance!
+ llassert(isint && listener);
+ }
+ // pop the int "event.listener" key
+ lua_pop(L, 1);
+ return listener;
+}
+LuaListener::ptr_t LuaState::obtainListener(lua_State* L)
+{
+ auto listener{ getListener(L) };
+ if (! listener)
+ {
+ // have to use one more stack slot
+ luaL_checkstack(L, 1, nullptr);
+ // instantiate a new LuaListener, binding the L state -- but use a
+ // no-op deleter: we do NOT want this ptr_t to manage the lifespan of
+ // this new LuaListener!
+ listener.reset(new LuaListener(L), [](LuaListener*){});
+ // set its key in the field where we'll look for it later
+ lua_pushinteger(L, listener->getKey());
+ lua_setfield(L, LUA_REGISTRYINDEX, "event.listener");
+ }
+ return listener;
+}
+
+/*****************************************************************************
+* LuaPopper class
+*****************************************************************************/
LuaPopper::~LuaPopper()
{
if (mCount)
@@ -508,15 +549,21 @@ LuaPopper::~LuaPopper()
}
}
+/*****************************************************************************
+* LuaFunction class
+*****************************************************************************/
LuaFunction::LuaFunction(const std::string_view& name, lua_CFunction function,
const std::string_view& helptext)
{
- getRegistry().emplace(name, Registry::mapped_type{ function, helptext });
+ const auto& [registry, lookup] = getState();
+ registry.emplace(name, Registry::mapped_type{ function, helptext });
+ lookup.emplace(function, name);
}
void LuaFunction::init(lua_State* L)
{
- for (const auto& [name, pair]: getRegistry())
+ const auto& [registry, lookup] = getRState();
+ for (const auto& [name, pair]: registry)
{
const auto& [funcptr, helptext] = pair;
lua_register(L, name.c_str(), funcptr);
@@ -527,19 +574,148 @@ lua_CFunction LuaFunction::get(const std::string& key)
{
// use find() instead of subscripting to avoid creating an entry for
// unknown key
- const auto& registry{ getRegistry() };
+ const auto& [registry, lookup] = getState();
auto found{ registry.find(key) };
return (found == registry.end())? nullptr : found->second.first;
}
-LuaFunction::Registry& LuaFunction::getRegistry()
+std::pair<LuaFunction::Registry&, LuaFunction::Lookup&> LuaFunction::getState()
{
- // use a function-local static to ensure it's initialized
+ // use function-local statics to ensure they're initialized
static Registry registry;
- return registry;
+ static Lookup lookup;
+ return { registry, lookup };
+}
+
+/*****************************************************************************
+* help()
+*****************************************************************************/
+lua_function(help,
+ "help(): list viewer's Lua functions\n"
+ "help(function): show help string for specific function")
+{
+ auto& luapump{ LLEventPumps::instance().obtain("lua output") };
+ const auto& [registry, lookup]{ LuaFunction::getRState() };
+ if (! lua_gettop(L))
+ {
+ // no arguments passed: list all lua_functions
+ for (const auto& [name, pair] : registry)
+ {
+ const auto& [fptr, helptext] = pair;
+ luapump.post(helptext);
+ }
+ }
+ else
+ {
+ // arguments passed: list each of the specified lua_functions
+ for (int idx = 1, top = lua_gettop(L); idx <= top; ++idx)
+ {
+ std::string arg{ stringize("<unknown ", lua_typename(L, lua_type(L, idx)), ">") };
+ if (lua_type(L, idx) == LUA_TSTRING)
+ {
+ arg = lua_tostdstring(L, idx);
+ }
+ else if (lua_type(L, idx) == LUA_TFUNCTION)
+ {
+ // Caller passed the actual function instead of its string
+ // name. A Lua function is an anonymous callable object; it
+ // has a name only by assigment. You can't ask Lua for a
+ // function's name, which is why our constructor maintains a
+ // reverse Lookup map.
+ auto function{ lua_tocfunction(L, idx) };
+ if (auto found = lookup.find(function); found != lookup.end())
+ {
+ // okay, pass found name to lookup below
+ arg = found->second;
+ }
+ }
+
+ if (auto found = registry.find(arg); found != registry.end())
+ {
+ luapump.post(found->second.second);
+ }
+ else
+ {
+ luapump.post(arg + ": NOT FOUND");
+ }
+ }
+ // pop all arguments
+ lua_settop(L, 0);
+ }
+ return 0; // void return
}
+/*****************************************************************************
+* leaphelp()
+*****************************************************************************/
+lua_function(
+ leaphelp,
+ "leaphelp(): list viewer's LEAP APIs\n"
+ "leaphelp(api): show help for specific api string name")
+{
+ LLSD request;
+ int top{ lua_gettop(L) };
+ if (top)
+ {
+ request = llsd::map("op", "getAPI", "api", lua_tostdstring(L, 1));
+ }
+ else
+ {
+ request = llsd::map("op", "getAPIs");
+ }
+ // pop all args
+ lua_settop(L, 0);
+
+ auto& outpump{ LLEventPumps::instance().obtain("lua output") };
+ auto listener{ LuaState::obtainListener(L) };
+ LLEventStream replyPump("leaphelp", true);
+ // ask the LuaListener's LeapListener and suspend calling coroutine until reply
+ auto reply{ llcoro::postAndSuspend(request, listener->getCommandName(), replyPump, "reply") };
+ reply.erase("reqid");
+
+ if (auto error = reply["error"]; error.isString())
+ {
+ outpump.post(error.asString());
+ return 0;
+ }
+
+ if (top)
+ {
+ // caller wants a specific API
+ outpump.post(stringize(reply["name"].asString(), ":\n", reply["desc"].asString()));
+ for (const auto& opmap : llsd::inArray(reply["ops"]))
+ {
+ std::ostringstream reqstr;
+ auto req{ opmap["required"] };
+ if (req.isArray())
+ {
+ const char* sep = " (requires ";
+ for (const auto& [reqkey, reqval] : llsd::inMap(req))
+ {
+ reqstr << sep << reqkey;
+ sep = ", ";
+ }
+ reqstr << ")";
+ }
+ outpump.post(stringize("---- ", reply["key"].asString(), " == '",
+ opmap["name"].asString(), "'", reqstr.str(), ":\n",
+ opmap["desc"].asString()));
+ }
+ }
+ else
+ {
+ // caller wants a list of APIs
+ for (const auto& [name, data] : llsd::inMap(reply))
+ {
+ outpump.post(stringize("==== ", name, ":\n", data["desc"].asString()));
+ }
+ }
+ return 0; // void return
+}
+/*****************************************************************************
+* lua_what
+*****************************************************************************/
std::ostream& operator<<(std::ostream& out, const lua_what& self)
{
switch (lua_type(self.L, self.index))
@@ -595,6 +771,9 @@ std::ostream& operator<<(std::ostream& out, const lua_what& self)
return out;
}
+/*****************************************************************************
+* lua_stack
+*****************************************************************************/
std::ostream& operator<<(std::ostream& out, const lua_stack& self)
{
const char* sep = "stack: [";
@@ -607,6 +786,9 @@ std::ostream& operator<<(std::ostream& out, const lua_stack& self)
return out;
}
+/*****************************************************************************
+* DebugExit
+*****************************************************************************/
DebugExit::~DebugExit()
{
LL_DEBUGS("Lua") << "exit " << mName << LL_ENDL;
diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h
index c23bf533ba..7973a769be 100644
--- a/indra/llcommon/lua_function.h
+++ b/indra/llcommon/lua_function.h
@@ -17,8 +17,11 @@
#include "luau/luaconf.h"
#include "luau/lualib.h"
#include "stringize.h"
+#include <memory> // std::shared_ptr
#include <utility> // std::pair
+class LuaListener;
+
#define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n)))
#define lua_rawlen lua_objlen
@@ -83,6 +86,17 @@ public:
operator lua_State*() const { return mState; }
+ // Return LuaListener for this LuaState if we already have one, else empty
+ // shared_ptr.
+ std::shared_ptr<LuaListener> getListener() { return getListener(mState); }
+ // Find or create LuaListener for this LuaState, returning its ptr_t.
+ std::shared_ptr<LuaListener> obtainListener() { return obtainListener(mState); }
+ // Return LuaListener for passed lua_State if we already have one, else
+ // empty shared_ptr.
+ static std::shared_ptr<LuaListener> getListener(lua_State* L);
+ // Find or create LuaListener for passed lua_State, returning its ptr_t.
+ static std::shared_ptr<LuaListener> obtainListener(lua_State* L);
+
private:
script_finished_fn mCallback;
lua_State* mState;
@@ -131,9 +145,13 @@ public:
static lua_CFunction get(const std::string& key);
-private:
+protected:
using Registry = std::map<std::string, std::pair<lua_CFunction, std::string>>;
- static Registry& getRegistry();
+ using Lookup = std::map<lua_CFunction, std::string>;
+ static std::pair<const Registry&, const Lookup&> getRState() { return getState(); }
+
+private:
+ static std::pair<Registry&, Lookup&> getState();
};
/**
diff --git a/indra/llcommon/stringize.h b/indra/llcommon/stringize.h
index c0b13135f9..63d44a7272 100644
--- a/indra/llcommon/stringize.h
+++ b/indra/llcommon/stringize.h
@@ -88,13 +88,13 @@ struct gstringize_impl
};
// partially specialize for a single STRING argument -
-// note that ll_convert<T>(T) already handles the trivial case
+// note that ll_convert_to<T>(T) already handles the trivial case
template <typename OUTCHAR, typename INCHAR>
struct gstringize_impl<OUTCHAR, std::basic_string<INCHAR>>
{
auto operator()(const std::basic_string<INCHAR>& arg)
{
- return ll_convert<std::basic_string<OUTCHAR>>(arg);
+ return ll_convert_to<std::basic_string<OUTCHAR>>(arg);
}
};
@@ -105,7 +105,7 @@ struct gstringize_impl<OUTCHAR, INCHAR*>
{
auto operator()(const INCHAR* arg)
{
- return ll_convert<std::basic_string<OUTCHAR>>(arg);
+ return ll_convert_to<std::basic_string<OUTCHAR>>(arg);
}
};
diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp
index 3d2a426913..ccd04f83e7 100644
--- a/indra/llui/lltexteditor.cpp
+++ b/indra/llui/lltexteditor.cpp
@@ -1504,7 +1504,8 @@ void LLTextEditor::cleanStringForPaste(LLWString & clean_string)
}
-void LLTextEditor::pasteTextWithLinebreaks(LLWString & clean_string)
+template <>
+void LLTextEditor::pasteTextWithLinebreaks<LLWString>(const LLWString & clean_string)
{
std::basic_string<llwchar>::size_type start = 0;
std::basic_string<llwchar>::size_type pos = clean_string.find('\n',start);
diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h
index ec2b9a4b7d..01b5ce5fbd 100644
--- a/indra/llui/lltexteditor.h
+++ b/indra/llui/lltexteditor.h
@@ -34,6 +34,7 @@
#include "llstyle.h"
#include "lleditmenuhandler.h"
#include "llviewborder.h" // for params
+#include "llstring.h"
#include "lltextbase.h"
#include "lltextvalidate.h"
@@ -294,10 +295,19 @@ private:
//
// Methods
//
- void pasteHelper(bool is_primary);
+ void pasteHelper(bool is_primary);
void cleanStringForPaste(LLWString & clean_string);
- void pasteTextWithLinebreaks(LLWString & clean_string);
+public:
+ template <typename STRINGTYPE>
+ void pasteTextWithLinebreaks(const STRINGTYPE& clean_string)
+ {
+ pasteTextWithLinebreaks<LLWString>(ll_convert(clean_string));
+ }
+ template <>
+ void pasteTextWithLinebreaks<LLWString>(const LLWString & clean_string);
+
+private:
void onKeyStroke();
// Concrete TextCmd sub-classes used by the LLTextEditor base class
diff --git a/indra/newview/llfloaterluadebug.cpp b/indra/newview/llfloaterluadebug.cpp
index 7d56d8e618..3ebe40f80b 100644
--- a/indra/newview/llfloaterluadebug.cpp
+++ b/indra/newview/llfloaterluadebug.cpp
@@ -57,7 +57,7 @@ BOOL LLFloaterLUADebug::postBuild()
.listen("LLFloaterLUADebug",
[mResultOutput=mResultOutput](const LLSD& data)
{
- mResultOutput->insertText(data.asString());
+ mResultOutput->pasteTextWithLinebreaks(data.asString());
mResultOutput->addLineBreakChar(true);
return false;
});
@@ -122,13 +122,13 @@ void LLFloaterLUADebug::completion(int count, const LLSD& result)
{
// error: show error message
mResultOutput->insertText("*** ");
- mResultOutput->insertText(result.asString());
+ mResultOutput->pasteTextWithLinebreaks(result.asString());
return;
}
if (count == 1)
{
// single result
- mResultOutput->insertText(stringize(result));
+ mResultOutput->pasteTextWithLinebreaks(stringize(result));
return;
}
// 0 or multiple results
@@ -136,7 +136,7 @@ void LLFloaterLUADebug::completion(int count, const LLSD& result)
for (const auto& item : llsd::inArray(result))
{
mResultOutput->insertText(sep);
- mResultOutput->insertText(stringize(item));
+ mResultOutput->pasteTextWithLinebreaks(stringize(item));
sep = ", ";
}
}
diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp
index d58ee5ca5f..c6a900aa81 100644
--- a/indra/newview/llluamanager.cpp
+++ b/indra/newview/llluamanager.cpp
@@ -197,34 +197,7 @@ lua_function(listen_events,
// back to the main thread from a coroutine thread?
lua_State* mainthread{ L };
- luaL_checkstack(mainthread, 1, nullptr);
- LuaListener::ptr_t listener;
- // Does the main thread already have a LuaListener stored in the registry?
- // That is, has this Lua chunk already called listen_events()?
- auto keytype{ lua_getfield(mainthread, LUA_REGISTRYINDEX, "event.listener") };
- llassert(keytype == LUA_TNIL || keytype == LUA_TNUMBER);
- if (keytype == LUA_TNUMBER)
- {
- // We do already have a LuaListener. Retrieve it.
- int isint;
- listener = LuaListener::getInstance(lua_tointegerx(mainthread, -1, &isint));
- // pop the int "event.listener" key
- lua_pop(mainthread, 1);
- // Nobody should have destroyed this LuaListener instance!
- llassert(isint && listener);
- }
- else
- {
- // pop the nil "event.listener" key
- lua_pop(mainthread, 1);
- // instantiate a new LuaListener, binding the mainthread state -- but
- // use a no-op deleter: we do NOT want to delete this new LuaListener
- // on return from listen_events()!
- listener.reset(new LuaListener(mainthread), [](LuaListener*){});
- // set its key in the field where we'll look for it later
- lua_pushinteger(mainthread, listener->getKey());
- lua_setfield(mainthread, LUA_REGISTRYINDEX, "event.listener");
- }
+ auto listener{ LuaState::obtainListener(mainthread) };
// Now that we've found or created our LuaListener, store the passed Lua
// function as the callback. Beware: our caller passed the function on L's