From df41a3a7dfe7f3bc69eabef3a4afe0e9f2a8d8e1 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 17 Aug 2023 09:14:54 -0400 Subject: DRTVWR-588: Finally ditch LL_USE_SYSTEM_RAND code in llrand.cpp. This conditional code hasn't been used since June 2008, possibly even earlier. --- indra/llcommon/llrand.cpp | 104 +++++++++++++++++----------------------------- 1 file changed, 39 insertions(+), 65 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llrand.cpp b/indra/llcommon/llrand.cpp index cb28a8f5c3..33afc50cf7 100644 --- a/indra/llcommon/llrand.cpp +++ b/indra/llcommon/llrand.cpp @@ -58,46 +58,14 @@ * to restore uniform distribution. */ -// *NOTE: The system rand implementation is probably not correct. -#define LL_USE_SYSTEM_RAND 0 +static LLRandLagFib2281 gRandomGenerator(LLUUID::getRandomSeed()); -#if LL_USE_SYSTEM_RAND -#include -#endif +// no default implementation, only specific F64 and F32 specializations +template +inline REAL ll_internal_random(); -#if LL_USE_SYSTEM_RAND -class LLSeedRand -{ -public: - LLSeedRand() - { -#if LL_WINDOWS - srand(LLUUID::getRandomSeed()); -#else - srand48(LLUUID::getRandomSeed()); -#endif - } -}; -static LLSeedRand sRandomSeeder; -inline F64 ll_internal_random_double() -{ -#if LL_WINDOWS - return (F64)rand() / (F64)RAND_MAX; -#else - return drand48(); -#endif -} -inline F32 ll_internal_random_float() -{ -#if LL_WINDOWS - return (F32)rand() / (F32)RAND_MAX; -#else - return (F32)drand48(); -#endif -} -#else -static LLRandLagFib2281 gRandomGenerator(LLUUID::getRandomSeed()); -inline F64 ll_internal_random_double() +template <> +inline F64 ll_internal_random() { // *HACK: Through experimentation, we have found that dual core // CPUs (or at least multi-threaded processes) seem to @@ -108,15 +76,35 @@ inline F64 ll_internal_random_double() return rv; } +template <> +inline F32 ll_internal_random() +{ + return F32(ll_internal_random()); +} + +/*------------------------------ F64 aliases -------------------------------*/ +inline F64 ll_internal_random_double() +{ + return ll_internal_random(); +} + +F64 ll_drand() +{ + return ll_internal_random_double(); +} + +/*------------------------------ F32 aliases -------------------------------*/ inline F32 ll_internal_random_float() { - // The clamping rules are described above. - F32 rv = (F32)gRandomGenerator(); - if(!((rv >= 0.0f) && (rv < 1.0f))) return fmod(rv, 1.f); - return rv; + return ll_internal_random(); +} + +F32 ll_frand() +{ + return ll_internal_random_float(); } -#endif +/*-------------------------- clamped random range --------------------------*/ S32 ll_rand() { return ll_rand(RAND_MAX); @@ -130,42 +118,28 @@ S32 ll_rand(S32 val) return rv; } -F32 ll_frand() -{ - return ll_internal_random_float(); -} - -F32 ll_frand(F32 val) +template +REAL ll_grand(REAL val) { // The clamping rules are described above. - F32 rv = ll_internal_random_float() * val; + REAL rv = ll_internal_random() * val; if(val > 0) { - if(rv >= val) return 0.0f; + if(rv >= val) return REAL(); } else { - if(rv <= val) return 0.0f; + if(rv <= val) return REAL(); } return rv; } -F64 ll_drand() +F32 ll_frand(F32 val) { - return ll_internal_random_double(); + return ll_grand(val); } F64 ll_drand(F64 val) { - // The clamping rules are described above. - F64 rv = ll_internal_random_double() * val; - if(val > 0) - { - if(rv >= val) return 0.0; - } - else - { - if(rv <= val) return 0.0; - } - return rv; + return ll_grand(val); } -- cgit v1.2.3 From 0f8b8fd7a338272e3464e809b94eb0443d99e275 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 17 Aug 2023 09:28:53 -0400 Subject: DRTVWR-588: Eliminate APR and Boost.Phoenix from NamedTempFile. NamedTempFile used to use APR calls to discover a suitable temp directory, synthesize a temp filename template, generate the unique file, write its content and ultimately delete it. This required a reference to gAPRPoolp as the default value of an optional constructor argument in case some usage demanded an alternative APR memory pool. It also used Boost.Phoenix placeholders to magically synthesize a callable. Replace APR calls with Boost.Filesystem; replace Boost.Phoenix with lambdas. Break out unique path generation logic as static NamedTempFile::temp_path(). In a nod to GitHub Actions builds, honor RUNNER_TEMP environment variable if set. test.cpp's RecordToTempFile need no longer pass an apr_pool_t* to NamedTempFile. NamedTempFile's constructor now accepts an optional suffix, making subclass NamedExtTempFile nearly trivial. It no longer needs to create or remove a symlink, for which it used to use APR calls. llprocess_test.cpp's NamedTempDir used to use Python's tempfile.mkdtemp() to create a temp directory, and apr_dir_remove() to destroy it. Replace both with NamedTempFile::temp_path() and Boost.Filesystem. Also add diagnostic output for LLProcess test failure. If llprocess_test cannot launch a child process, notice the APR_LOG environment variable recognized by our patched apr_suite to engage logging, and report the contents of that file. --- indra/llcommon/tests/llprocess_test.cpp | 70 ++++++++++---- indra/test/namedtempfile.h | 158 ++++++++++++-------------------- indra/test/test.cpp | 2 +- 3 files changed, 111 insertions(+), 119 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 81449b4a42..c48d913069 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -151,8 +151,38 @@ struct PythonProcessLauncher /// Launch Python script; verify that it launched void launch() { - mPy = LLProcess::create(mParams); - tut::ensure(STRINGIZE("Couldn't launch " << mDesc << " script"), bool(mPy)); + try + { + mPy = LLProcess::create(mParams); + tut::ensure(STRINGIZE("Couldn't launch " << mDesc << " script"), bool(mPy)); + } + catch (const tut::failure&) + { + // On Windows, if APR_LOG is set, our version of APR's + // apr_create_proc() logs to the specified file. If this test + // failed, try to report that log. + const char* APR_LOG = getenv("APR_LOG"); + if (APR_LOG && *APR_LOG) + { + std::ifstream inf(APR_LOG); + if (! inf.is_open()) + { + LL_WARNS() << "Couldn't open '" << APR_LOG << "'" << LL_ENDL; + } + else + { + LL_WARNS() << "==============================" << LL_ENDL; + LL_WARNS() << "From '" << APR_LOG << "':" << LL_ENDL; + std::string line; + while (std::getline(inf, line)) + { + LL_WARNS() << line << LL_ENDL; + } + LL_WARNS() << "==============================" << LL_ENDL; + } + } + throw; + } } /// Run Python script and wait for it to complete. @@ -214,30 +244,26 @@ static std::string python_out(const std::string& desc, const CONTENT& script) class NamedTempDir: public boost::noncopyable { public: - // Use python() function to create a temp directory: I've found - // nothing in either Boost.Filesystem or APR quite like Python's - // tempfile.mkdtemp(). - // Special extra bonus: on Mac, mkdtemp() reports a pathname - // starting with /var/folders/something, whereas that's really a - // symlink to /private/var/folders/something. Have to use - // realpath() to compare properly. NamedTempDir(): - mPath(python_out("mkdtemp()", - "from __future__ import with_statement\n" - "import os.path, sys, tempfile\n" - "with open(sys.argv[1], 'w') as f:\n" - " f.write(os.path.normcase(os.path.normpath(os.path.realpath(tempfile.mkdtemp()))))\n")) - {} + mPath(NamedTempFile::temp_path()), + mCreated(boost::filesystem::create_directories(mPath)) + { + mPath = boost::filesystem::canonical(mPath); + } ~NamedTempDir() { - aprchk(apr_dir_remove(mPath.c_str(), gAPRPoolp)); + if (mCreated) + { + boost::filesystem::remove_all(mPath); + } } - std::string getName() const { return mPath; } + std::string getName() const { return mPath.string(); } private: - std::string mPath; + boost::filesystem::path mPath; + bool mCreated; }; /***************************************************************************** @@ -565,7 +591,13 @@ namespace tut " f.write(os.path.normcase(os.path.normpath(os.getcwd())))\n"); // Before running, call setWorkingDirectory() py.mParams.cwd = tempdir.getName(); - ensure_equals("os.getcwd()", py.run_read(), tempdir.getName()); + std::string expected{ tempdir.getName() }; +#if LL_WINDOWS + // SIGH, don't get tripped up by "C:" != "c:" -- + // but on the Mac, using tolower() fails because "/users" != "/Users"! + expected = utf8str_tolower(expected); +#endif + ensure_equals("os.getcwd()", py.run_read(), expected); } template<> template<> diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index 7d59cad32c..525a35000d 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -13,15 +13,16 @@ #define LL_NAMEDTEMPFILE_H #include "llerror.h" -#include "llapr.h" -#include "apr_file_io.h" +#include "llstring.h" +#include "stringize.h" #include -#include -#include -#include +#include +#include #include +#include #include #include +#include /** * Create a text file with specified content "somewhere in the @@ -31,134 +32,106 @@ class NamedTempFile: public boost::noncopyable { LOG_CLASS(NamedTempFile); public: - NamedTempFile(const std::string& pfx, const std::string& content, apr_pool_t* pool=gAPRPoolp): - mPool(pool) + NamedTempFile(const std::string_view& pfx, + const std::string_view& content, + const std::string_view& sfx=std::string_view("")) { - createFile(pfx, boost::phoenix::placeholders::arg1 << content); + createFile(pfx, [&content](std::ostream& out){ out << content; }, sfx); } - // Disambiguate when passing string literal - NamedTempFile(const std::string& pfx, const char* content, apr_pool_t* pool=gAPRPoolp): - mPool(pool) + // Disambiguate when passing string literal -- unclear why a string + // literal should be ambiguous wrt std::string_view and Streamer + NamedTempFile(const std::string_view& pfx, + const char* content, + const std::string_view& sfx=std::string_view("")) { - createFile(pfx, boost::phoenix::placeholders::arg1 << content); + createFile(pfx, [&content](std::ostream& out){ out << content; }, sfx); } // Function that accepts an ostream ref and (presumably) writes stuff to // it, e.g.: // (boost::phoenix::placeholders::arg1 << "the value is " << 17 << '\n') - typedef boost::function Streamer; + typedef std::function Streamer; - NamedTempFile(const std::string& pfx, const Streamer& func, apr_pool_t* pool=gAPRPoolp): - mPool(pool) + NamedTempFile(const std::string_view& pfx, + const Streamer& func, + const std::string_view& sfx=std::string_view("")) { - createFile(pfx, func); + createFile(pfx, func, sfx); } virtual ~NamedTempFile() { - ll_apr_assert_status(apr_file_remove(mPath.c_str(), mPool)); + boost::filesystem::remove(mPath); } - virtual std::string getName() const { return mPath; } + std::string getName() const { return mPath.string(); } void peep() { std::cout << "File '" << mPath << "' contains:\n"; - std::ifstream reader(mPath.c_str()); + boost::filesystem::ifstream reader(mPath); std::string line; while (std::getline(reader, line)) std::cout << line << '\n'; std::cout << "---\n"; } + static boost::filesystem::path temp_path(const std::string_view& pfx="", + const std::string_view& sfx="") + { + // This variable is set by GitHub actions and is the recommended place + // to put temp files belonging to an actions job. + const char* RUNNER_TEMP = getenv("RUNNER_TEMP"); + boost::filesystem::path tempdir{ + // if RUNNER_TEMP is set and not empty + (RUNNER_TEMP && *RUNNER_TEMP)? + boost::filesystem::path(RUNNER_TEMP) : // use RUNNER_TEMP if available + boost::filesystem::temp_directory_path()}; // else canonical temp dir + boost::filesystem::path tempname{ + // use filename template recommended by unique_path() doc, but + // with underscores instead of hyphens: some use cases involve + // temporary Python scripts + tempdir / stringize(pfx, "%%%%_%%%%_%%%%_%%%%", sfx) }; + return boost::filesystem::unique_path(tempname); + } + protected: - void createFile(const std::string& pfx, const Streamer& func) + void createFile(const std::string_view& pfx, + const Streamer& func, + const std::string_view& sfx) { // Create file in a temporary place. - const char* tempdir = NULL; - ll_apr_assert_status(apr_temp_dir_get(&tempdir, mPool)); - - // Construct a temp filename template in that directory. - char *tempname = NULL; - ll_apr_assert_status(apr_filepath_merge(&tempname, - tempdir, - (pfx + "XXXXXX").c_str(), - 0, - mPool)); - - // Create a temp file from that template. - apr_file_t* fp = NULL; - ll_apr_assert_status(apr_file_mktemp(&fp, - tempname, - APR_CREATE | APR_WRITE | APR_EXCL, - mPool)); - // apr_file_mktemp() alters tempname with the actual name. Not until - // now is it valid to capture as our mPath. - mPath = tempname; - + mPath = temp_path(pfx, sfx); + boost::filesystem::ofstream out{ mPath }; // Write desired content. - std::ostringstream out; - // Stream stuff to it. func(out); - - std::string data(out.str()); - apr_size_t writelen(data.length()); - ll_apr_assert_status(apr_file_write(fp, data.c_str(), &writelen)); - ll_apr_assert_status(apr_file_close(fp)); - llassert_always(writelen == data.length()); } - std::string mPath; - apr_pool_t* mPool; + boost::filesystem::path mPath; }; /** * Create a NamedTempFile with a specified filename extension. This is useful * when, for instance, you must be able to use the file in a Python import * statement. - * - * A NamedExtTempFile actually has two different names. We retain the original - * no-extension name as a placeholder in the temp directory to ensure - * uniqueness; to that we link the name plus the desired extension. Naturally, - * both must be removed on destruction. */ class NamedExtTempFile: public NamedTempFile { LOG_CLASS(NamedExtTempFile); public: - NamedExtTempFile(const std::string& ext, const std::string& content, apr_pool_t* pool=gAPRPoolp): - NamedTempFile(remove_dot(ext), content, pool), - mLink(mPath + ensure_dot(ext)) - { - linkto(mLink); - } + NamedExtTempFile(const std::string& ext, const std::string_view& content): + NamedTempFile(remove_dot(ext), content, ensure_dot(ext)) + {} // Disambiguate when passing string literal - NamedExtTempFile(const std::string& ext, const char* content, apr_pool_t* pool=gAPRPoolp): - NamedTempFile(remove_dot(ext), content, pool), - mLink(mPath + ensure_dot(ext)) - { - linkto(mLink); - } + NamedExtTempFile(const std::string& ext, const char* content): + NamedTempFile(remove_dot(ext), content, ensure_dot(ext)) + {} - NamedExtTempFile(const std::string& ext, const Streamer& func, apr_pool_t* pool=gAPRPoolp): - NamedTempFile(remove_dot(ext), func, pool), - mLink(mPath + ensure_dot(ext)) - { - linkto(mLink); - } - - virtual ~NamedExtTempFile() - { - ll_apr_assert_status(apr_file_remove(mLink.c_str(), mPool)); - } - - // Since the caller has gone to the trouble to create the name with the - // extension, that should be the name we return. In this class, mPath is - // just a placeholder to ensure that future createFile() calls won't - // collide. - virtual std::string getName() const { return mLink; } + NamedExtTempFile(const std::string& ext, const Streamer& func): + NamedTempFile(remove_dot(ext), func, ensure_dot(ext)) + {} static std::string ensure_dot(const std::string& ext) { @@ -175,7 +148,7 @@ public: { return ext; } - return std::string(".") + ext; + return "." + ext; } static std::string remove_dot(const std::string& ext) @@ -187,19 +160,6 @@ public: } return ext.substr(found); } - -private: - void linkto(const std::string& path) - { - // This method assumes that since mPath (without extension) is - // guaranteed by apr_file_mktemp() to be unique, then (mPath + any - // extension) is also unique. This is likely, though not guaranteed: - // files could be created in the same temp directory other than by - // this class. - ll_apr_assert_status(apr_file_link(mPath.c_str(), path.c_str())); - } - - std::string mLink; }; #endif /* ! defined(LL_NAMEDTEMPFILE_H) */ diff --git a/indra/test/test.cpp b/indra/test/test.cpp index bb48216b2b..9dd33c574d 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -100,7 +100,7 @@ public: RecordToTempFile(apr_pool_t* pPool) : LLError::Recorder(), boost::noncopyable(), - mTempFile("log", "", pPool), + mTempFile("log", ""), mFile(mTempFile.getName().c_str()) { } -- cgit v1.2.3 From 48759438b747c24b536cd099d65ab0c9ea720a38 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 17 Aug 2023 09:53:01 -0400 Subject: DRTVWR-588: Remove Boost Phoenix, Bind and Assign from some tests. llsdserialize_test used Boost.Foreach, Boost.Function and Boost.Bind. llleap_test used Boost.Assign. Both used Boost.Phoenix. Replace Boost.Foreach with range 'for'. Replace Boost.Function with std::function. Replace Boost.Assign with initializer lists. Replace Boost.Bind and Boost.Phoenix with lambdas. --- indra/llcommon/tests/llleap_test.cpp | 251 ++++++++++++++-------------- indra/llcommon/tests/llsdserialize_test.cpp | 39 ++--- 2 files changed, 134 insertions(+), 156 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 7ee36a9ea6..671873e982 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -17,8 +17,6 @@ // std headers #include // external library headers -#include -#include // other Linden headers #include "../test/lltut.h" #include "../test/namedtempfile.h" @@ -30,10 +28,6 @@ #include "stringize.h" #include "StringVec.h" -using boost::assign::list_of; - -StringVec sv(const StringVec& listof) { return listof; } - #if defined(LL_WINDOWS) #define sleep(secs) _sleep((secs) * 1000) @@ -104,7 +98,7 @@ namespace tut llleap_data(): reader(".py", // This logic is adapted from vita.viewerclient.receiveEvent() - boost::phoenix::placeholders::arg1 << + [](std::ostream& out){ out << "import re\n" "import os\n" "import sys\n" @@ -188,7 +182,7 @@ namespace tut "def request(pump, data):\n" " # we expect 'data' is a dict\n" " data['reply'] = _reply\n" - " send(pump, data)\n"), + " send(pump, data)\n";}), // Get the actual pathname of the NamedExtTempFile and trim off // the ".py" extension. (We could cache reader.getName() in a // separate member variable, but I happen to know getName() just @@ -213,14 +207,14 @@ namespace tut void object::test<1>() { set_test_name("multiple LLLeap instances"); - NamedTempFile script("py", - "import time\n" - "time.sleep(1)\n"); + NamedExtTempFile script("py", + "import time\n" + "time.sleep(1)\n"); LLLeapVector instances; instances.push_back(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))->getWeak()); + StringVec{PYTHON, script.getName()})->getWeak()); instances.push_back(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))->getWeak()); + StringVec{PYTHON, script.getName()})->getWeak()); // In this case we're simply establishing that two LLLeap instances // can coexist without throwing exceptions or bombing in any other // way. Wait for them to terminate. @@ -231,10 +225,10 @@ namespace tut void object::test<2>() { set_test_name("stderr to log"); - NamedTempFile script("py", - "import sys\n" - "sys.stderr.write('''Hello from Python!\n" - "note partial line''')\n"); + NamedExtTempFile script("py", + "import sys\n" + "sys.stderr.write('''Hello from Python!\n" + "note partial line''')\n"); StringVec vcommand{ PYTHON, script.getName() }; CaptureLog log(LLError::LEVEL_INFO); waitfor(LLLeap::create(get_test_name(), vcommand)); @@ -246,11 +240,11 @@ namespace tut void object::test<3>() { set_test_name("bad stdout protocol"); - NamedTempFile script("py", - "print('Hello from Python!')\n"); + NamedExtTempFile script("py", + "print('Hello from Python!')\n"); CaptureLog log(LLError::LEVEL_WARN); waitfor(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))); + StringVec{PYTHON, script.getName()})); ensure_contains("error log line", log.messageWith("invalid protocol"), "Hello from Python!"); } @@ -259,13 +253,13 @@ namespace tut void object::test<4>() { set_test_name("leftover stdout"); - NamedTempFile script("py", - "import sys\n" - // note lack of newline - "sys.stdout.write('Hello from Python!')\n"); + NamedExtTempFile script("py", + "import sys\n" + // note lack of newline + "sys.stdout.write('Hello from Python!')\n"); CaptureLog log(LLError::LEVEL_WARN); waitfor(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))); + StringVec{PYTHON, script.getName()})); ensure_contains("error log line", log.messageWith("Discarding"), "Hello from Python!"); } @@ -274,12 +268,12 @@ namespace tut void object::test<5>() { set_test_name("bad stdout len prefix"); - NamedTempFile script("py", - "import sys\n" - "sys.stdout.write('5a2:something')\n"); + NamedExtTempFile script("py", + "import sys\n" + "sys.stdout.write('5a2:something')\n"); CaptureLog log(LLError::LEVEL_WARN); waitfor(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))); + StringVec{PYTHON, script.getName()})); ensure_contains("error log line", log.messageWith("invalid protocol"), "5a2:"); } @@ -381,17 +375,17 @@ namespace tut set_test_name("round trip"); AckAPI api; Result result; - NamedTempFile script("py", - boost::phoenix::placeholders::arg1 << - "from " << reader_module << " import *\n" - // make a request on our little API - "request(pump='" << api.getName() << "', data={})\n" - // wait for its response - "resp = get()\n" - "result = '' if resp == dict(pump=replypump(), data='ack')\\\n" - " else 'bad: ' + str(resp)\n" - "send(pump='" << result.getName() << "', data=result)\n"); - waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName())))); + NamedExtTempFile script("py", + [&](std::ostream& out){ out << + "from " << reader_module << " import *\n" + // make a request on our little API + "request(pump='" << api.getName() << "', data={})\n" + // wait for its response + "resp = get()\n" + "result = '' if resp == dict(pump=replypump(), data='ack')\\\n" + " else 'bad: ' + str(resp)\n" + "send(pump='" << result.getName() << "', data=result)\n";}); + waitfor(LLLeap::create(get_test_name(), StringVec{PYTHON, script.getName()})); result.ensure(); } @@ -419,38 +413,38 @@ namespace tut // iterations etc. in OS pipes and the LLLeap/LLProcess implementation. ReqIDAPI api; Result result; - NamedTempFile script("py", - boost::phoenix::placeholders::arg1 << - "import sys\n" - "from " << reader_module << " import *\n" - // Note that since reader imports llsd, this - // 'import *' gets us llsd too. - "sample = llsd.format_notation(dict(pump='" << - api.getName() << "', data=dict(reqid=999999, reply=replypump())))\n" - // The whole packet has length prefix too: "len:data" - "samplen = len(str(len(sample))) + 1 + len(sample)\n" - // guess how many messages it will take to - // accumulate BUFFERED_LENGTH - "count = int(" << BUFFERED_LENGTH << "/samplen)\n" - "print('Sending %s requests' % count, file=sys.stderr)\n" - "for i in range(count):\n" - " request('" << api.getName() << "', dict(reqid=i))\n" - // The assumption in this specific test that - // replies will arrive in the same order as - // requests is ONLY valid because the API we're - // invoking sends replies instantly. If the API - // had to wait for some external event before - // sending its reply, replies could arrive in - // arbitrary order, and we'd have to tick them - // off from a set. - "result = ''\n" - "for i in range(count):\n" - " resp = get()\n" - " if resp['data']['reqid'] != i:\n" - " result = 'expected reqid=%s in %s' % (i, resp)\n" - " break\n" - "send(pump='" << result.getName() << "', data=result)\n"); - waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName()))), + NamedExtTempFile script("py", + [&](std::ostream& out){ out << + "import sys\n" + "from " << reader_module << " import *\n" + // Note that since reader imports llsd, this + // 'import *' gets us llsd too. + "sample = llsd.format_notation(dict(pump='" << + api.getName() << "', data=dict(reqid=999999, reply=replypump())))\n" + // The whole packet has length prefix too: "len:data" + "samplen = len(str(len(sample))) + 1 + len(sample)\n" + // guess how many messages it will take to + // accumulate BUFFERED_LENGTH + "count = int(" << BUFFERED_LENGTH << "/samplen)\n" + "print('Sending %s requests' % count, file=sys.stderr)\n" + "for i in range(count):\n" + " request('" << api.getName() << "', dict(reqid=i))\n" + // The assumption in this specific test that + // replies will arrive in the same order as + // requests is ONLY valid because the API we're + // invoking sends replies instantly. If the API + // had to wait for some external event before + // sending its reply, replies could arrive in + // arbitrary order, and we'd have to tick them + // off from a set. + "result = ''\n" + "for i in range(count):\n" + " resp = get()\n" + " if resp['data']['reqid'] != i:\n" + " result = 'expected reqid=%s in %s' % (i, resp)\n" + " break\n" + "send(pump='" << result.getName() << "', data=result)\n";}); + waitfor(LLLeap::create(get_test_name(), StringVec{PYTHON, script.getName()}), 300); // needs more realtime than most tests result.ensure(); } @@ -462,65 +456,62 @@ namespace tut { ReqIDAPI api; Result result; - NamedTempFile script("py", - boost::phoenix::placeholders::arg1 << - "import sys\n" - "from " << reader_module << " import *\n" - // Generate a very large string value. - "desired = int(sys.argv[1])\n" - // 7 chars per item: 6 digits, 1 comma - "count = int((desired - 50)/7)\n" - "large = ''.join('%06d,' % i for i in range(count))\n" - // Pass 'large' as reqid because we know the API - // will echo reqid, and we want to receive it back. - "request('" << api.getName() << "', dict(reqid=large))\n" - "try:\n" - " resp = get()\n" - "except ParseError as e:\n" - " # try to find where e.data diverges from expectation\n" - // Normally we'd expect a 'pump' key in there, - // too, with value replypump(). But Python - // serializes keys in a different order than C++, - // so incoming data start with 'data'. - // Truthfully, though, if we get as far as 'pump' - // before we find a difference, something's very - // strange. - " expect = llsd.format_notation(dict(data=dict(reqid=large)))\n" - " chunk = 40\n" - " for offset in range(0, max(len(e.data), len(expect)), chunk):\n" - " if e.data[offset:offset+chunk] != \\\n" - " expect[offset:offset+chunk]:\n" - " print('Offset %06d: expect %r,\\n'\\\n" - " ' get %r' %\\\n" - " (offset,\n" - " expect[offset:offset+chunk],\n" - " e.data[offset:offset+chunk]),\n" - " file=sys.stderr)\n" - " break\n" - " else:\n" - " print('incoming data matches expect?!', file=sys.stderr)\n" - " send('" << result.getName() << "', '%s: %s' % (e.__class__.__name__, e))\n" - " sys.exit(1)\n" - "\n" - "echoed = resp['data']['reqid']\n" - "if echoed == large:\n" - " send('" << result.getName() << "', '')\n" - " sys.exit(0)\n" - // Here we know echoed did NOT match; try to find where - "for i in range(count):\n" - " start = 7*i\n" - " end = 7*(i+1)\n" - " if end > len(echoed)\\\n" - " or echoed[start:end] != large[start:end]:\n" - " send('" << result.getName() << "',\n" - " 'at offset %s, expected %r but got %r' %\n" - " (start, large[start:end], echoed[start:end]))\n" - "sys.exit(1)\n"); + NamedExtTempFile script("py", + [&](std::ostream& out){ out << + "import sys\n" + "from " << reader_module << " import *\n" + // Generate a very large string value. + "desired = int(sys.argv[1])\n" + // 7 chars per item: 6 digits, 1 comma + "count = int((desired - 50)/7)\n" + "large = ''.join('%06d,' % i for i in range(count))\n" + // Pass 'large' as reqid because we know the API + // will echo reqid, and we want to receive it back. + "request('" << api.getName() << "', dict(reqid=large))\n" + "try:\n" + " resp = get()\n" + "except ParseError as e:\n" + " # try to find where e.data diverges from expectation\n" + // Normally we'd expect a 'pump' key in there, + // too, with value replypump(). But Python + // serializes keys in a different order than C++, + // so incoming data start with 'data'. + // Truthfully, though, if we get as far as 'pump' + // before we find a difference, something's very + // strange. + " expect = llsd.format_notation(dict(data=dict(reqid=large)))\n" + " chunk = 40\n" + " for offset in range(0, max(len(e.data), len(expect)), chunk):\n" + " if e.data[offset:offset+chunk] != \\\n" + " expect[offset:offset+chunk]:\n" + " print('Offset %06d: expect %r,\\n'\\\n" + " ' get %r' %\\\n" + " (offset,\n" + " expect[offset:offset+chunk],\n" + " e.data[offset:offset+chunk]),\n" + " file=sys.stderr)\n" + " break\n" + " else:\n" + " print('incoming data matches expect?!', file=sys.stderr)\n" + " send('" << result.getName() << "', '%s: %s' % (e.__class__.__name__, e))\n" + " sys.exit(1)\n" + "\n" + "echoed = resp['data']['reqid']\n" + "if echoed == large:\n" + " send('" << result.getName() << "', '')\n" + " sys.exit(0)\n" + // Here we know echoed did NOT match; try to find where + "for i in range(count):\n" + " start = 7*i\n" + " end = 7*(i+1)\n" + " if end > len(echoed)\\\n" + " or echoed[start:end] != large[start:end]:\n" + " send('" << result.getName() << "',\n" + " 'at offset %s, expected %r but got %r' %\n" + " (start, large[start:end], echoed[start:end]))\n" + "sys.exit(1)\n";}); waitfor(LLLeap::create(test_name, - sv(list_of - (PYTHON) - (script.getName()) - (stringize(size)))), + StringVec{PYTHON, script.getName(), stringize(size)}), 180); // try a longer timeout result.ensure(); } diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 5dbcf4c9b8..f46682e2d7 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -44,18 +44,10 @@ typedef U32 uint32_t; #include "llstring.h" #endif -#include "boost/range.hpp" -#include "boost/foreach.hpp" -#include "boost/function.hpp" -#include "boost/bind.hpp" -#include "boost/phoenix/bind/bind_function.hpp" -#include "boost/phoenix/core/argument.hpp" -using namespace boost::phoenix; - -#include "../llsd.h" -#include "../llsdserialize.h" +#include "llsd.h" +#include "llsdserialize.h" #include "llsdutil.h" -#include "../llformat.h" +#include "llformat.h" #include "../test/lltut.h" #include "../test/namedtempfile.h" @@ -1697,13 +1689,6 @@ namespace tut struct TestPythonCompatible { TestPythonCompatible(): - // Note the peculiar insertion of __FILE__ into this string. Since - // this script is being written into a platform-dependent temp - // directory, we can't locate indra/lib/python relative to - // Python's __file__. Use __FILE__ instead, navigating relative - // to this C++ source file. Use Python raw-string syntax so - // Windows pathname backslashes won't mislead Python's string - // scanner. import_llsd("import os.path\n" "import sys\n" "from llbase import llsd\n") @@ -1801,7 +1786,7 @@ namespace tut // helper for test<3> static void writeLLSDArray(std::ostream& out, const LLSD& array) { - BOOST_FOREACH(LLSD item, llsd::inArray(array)) + for (LLSD item: llsd::inArray(array)) { LLSDSerialize::toNotation(item, out); // It's important to separate with newlines because Python's llsd @@ -1841,21 +1826,22 @@ namespace tut // Create an llsdXXXXXX file containing 'data' serialized to // notation. NamedTempFile file("llsd", - // NamedTempFile's boost::function constructor + // NamedTempFile's std::function constructor // takes a callable. To this callable it passes the // std::ostream with which it's writing the // NamedTempFile. - boost::bind(writeLLSDArray, _1, cdata)); + [cdata](std::ostream& out){ writeLLSDArray(out, cdata); }); python("read C++ notation", - placeholders::arg1 << + [this, pydata, &file](std::ostream& out) { + out << import_llsd << "def parse_each(iterable):\n" " for item in iterable:\n" " yield llsd.parse(item)\n" << pydata << // Don't forget raw-string syntax for Windows pathnames. - "verify(parse_each(open(r'" << file.getName() << "', 'rb')))\n"); + "verify(parse_each(open(r'" << file.getName() << "', 'rb')))\n"; }); } template<> template<> @@ -1869,7 +1855,8 @@ namespace tut NamedTempFile file("llsd", ""); python("write Python notation", - placeholders::arg1 << + [this, &file](std::ostream& out) { + out << import_llsd << "DATA = [\n" " 17,\n" @@ -1881,9 +1868,9 @@ namespace tut "]\n" // Don't forget raw-string syntax for Windows pathnames. // N.B. Using 'print' implicitly adds newlines. - "with open(r'" << file.getName() << "', 'w') as f:\n" + "with open(r'" << file.getName() << "', 'wb') as f:\n" " for item in DATA:\n" - " print(llsd.format_notation(item).decode(), file=f)\n"); + " print(llsd.format_notation(item), file=f)\n"; }); std::ifstream inf(file.getName().c_str()); LLSD item; -- cgit v1.2.3 From 3fbb1a496dd4aaadc11727832f3ab0cc8c64950f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 17 Aug 2023 10:03:16 -0400 Subject: DRTVWR-588: Remove some unused redundant timer functionality. LLEventTimer supported static run_every(), run_at() and run_after() methods to schedule future work. This can still be done by deriving from LLEventTimer, but is better accomplished with a WorkSchedule instance. These convenience methods, which encourage use of LLEventTimer insted of WorkSchedule, weren't used except by LLEventTimeout. Remove them and the LLEventTimer::Generic subclass used to implement them. Similarly, LLEventTimeout supported static post_every(), post_at() and post_after() methods based on LLEventTimer::run_every(), run_at() and run_after(). These weren't used either. LLRunner is a very old mechanism to schedule future work that seems to be unused. Research suggests that it's indirectly engaged only by LLDeferredChain, which isn't used. LLIOSleeper is tested but isn't otherwise used. Add a deprecation warning to llrun.h prior to excision. Also replace Boost.Bind with lambdas. --- indra/llcommon/lleventfilter.cpp | 34 ++++---------------- indra/llcommon/lleventfilter.h | 13 -------- indra/llcommon/lleventtimer.h | 69 +--------------------------------------- indra/llcommon/llrun.h | 11 +++++++ 4 files changed, 18 insertions(+), 109 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp index 4cded7f88e..14c9c51830 100644 --- a/indra/llcommon/lleventfilter.cpp +++ b/indra/llcommon/lleventfilter.cpp @@ -33,20 +33,19 @@ // STL headers // std headers // external library headers -#include // other Linden headers +#include "lldate.h" #include "llerror.h" // LL_ERRS +#include "lleventtimer.h" #include "llsdutil.h" // llsd_matches() #include "stringize.h" -#include "lleventtimer.h" -#include "lldate.h" /***************************************************************************** * LLEventFilter *****************************************************************************/ LLEventFilter::LLEventFilter(LLEventPump& source, const std::string& name, bool tweak): LLEventStream(name, tweak), - mSource(source.listen(getName(), boost::bind(&LLEventFilter::post, this, _1))) + mSource(source.listen(getName(), [this](const LLSD& event){ return post(event); })) { } @@ -93,7 +92,7 @@ void LLEventTimeoutBase::actionAfter(F32 seconds, const Action& action) if (! mMainloop.connected()) { LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop")); - mMainloop = mainloop.listen(getName(), boost::bind(&LLEventTimeoutBase::tick, this, _1)); + mMainloop = mainloop.listen(getName(), [this](const LLSD& event){ return tick(event); }); } } @@ -185,27 +184,6 @@ bool LLEventTimeout::countdownElapsed() const return mTimer.hasExpired(); } -LLEventTimer* LLEventTimeout::post_every(F32 period, const std::string& pump, const LLSD& data) -{ - return LLEventTimer::run_every( - period, - [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); }); -} - -LLEventTimer* LLEventTimeout::post_at(const LLDate& time, const std::string& pump, const LLSD& data) -{ - return LLEventTimer::run_at( - time, - [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); }); -} - -LLEventTimer* LLEventTimeout::post_after(F32 interval, const std::string& pump, const LLSD& data) -{ - return LLEventTimer::run_after( - interval, - [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); }); -} - /***************************************************************************** * LLEventBatch *****************************************************************************/ @@ -311,7 +289,7 @@ bool LLEventThrottleBase::post(const LLSD& event) // timeRemaining tells us how much longer it will be until // mInterval seconds since the last flush() call. At that time, // flush() deferred events. - alarmActionAfter(timeRemaining, boost::bind(&LLEventThrottleBase::flush, this)); + alarmActionAfter(timeRemaining, [this](){ flush(); }); } } return false; @@ -349,7 +327,7 @@ void LLEventThrottleBase::setInterval(F32 interval) // and if mAlarm is running, reset that too if (alarmRunning()) { - alarmActionAfter(timeRemaining, boost::bind(&LLEventThrottleBase::flush, this)); + alarmActionAfter(timeRemaining, [this](){ flush(); }); } } } diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h index 7613850fb2..437a4826d4 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -214,19 +214,6 @@ public: LLEventTimeout(); LLEventTimeout(LLEventPump& source); - /// using LLEventTimeout as namespace for free functions - /// Post event to specified LLEventPump every period seconds. Delete - /// returned LLEventTimer* to cancel. - static LLEventTimer* post_every(F32 period, const std::string& pump, const LLSD& data); - /// Post event to specified LLEventPump at specified future time. Call - /// LLEventTimer::getInstance(returned pointer) to check whether it's still - /// pending; if so, delete the pointer to cancel. - static LLEventTimer* post_at(const LLDate& time, const std::string& pump, const LLSD& data); - /// Post event to specified LLEventPump after specified interval. Call - /// LLEventTimer::getInstance(returned pointer) to check whether it's still - /// pending; if so, delete the pointer to cancel. - static LLEventTimer* post_after(F32 interval, const std::string& pump, const LLSD& data); - protected: virtual void setCountdown(F32 seconds); virtual bool countdownElapsed() const; diff --git a/indra/llcommon/lleventtimer.h b/indra/llcommon/lleventtimer.h index dbbfe0c6e6..b5d40a0622 100644 --- a/indra/llcommon/lleventtimer.h +++ b/indra/llcommon/lleventtimer.h @@ -24,7 +24,7 @@ * $/LicenseInfo$ */ -#ifndef LL_EVENTTIMER_H +#ifndef LL_EVENTTIMER_H #define LL_EVENTTIMER_H #include "stdtypes.h" @@ -47,76 +47,9 @@ public: static void updateClass(); - /// Schedule recurring calls to generic callable every period seconds. - /// Returns a pointer; if you delete it, cancels the recurring calls. - template - static LLEventTimer* run_every(F32 period, const CALLABLE& callable); - - /// Schedule a future call to generic callable. Returns a pointer. - /// CAUTION: The object referenced by that pointer WILL BE DELETED once - /// the callback has been called! LLEventTimer::getInstance(pointer) (NOT - /// pointer->getInstance(pointer)!) can be used to test whether the - /// pointer is still valid. If it is, deleting it will cancel the - /// callback. - template - static LLEventTimer* run_at(const LLDate& time, const CALLABLE& callable); - - /// Like run_at(), but after a time delta rather than at a timestamp. - /// Same CAUTION. - template - static LLEventTimer* run_after(F32 interval, const CALLABLE& callable); - protected: LLTimer mEventTimer; F32 mPeriod; - -private: - template - class Generic; }; -template -class LLEventTimer::Generic: public LLEventTimer -{ -public: - // making TIME generic allows engaging either LLEventTimer constructor - template - Generic(const TIME& time, bool once, const CALLABLE& callable): - LLEventTimer(time), - mOnce(once), - mCallable(callable) - {} - BOOL tick() override - { - mCallable(); - // true tells updateClass() to delete this instance - return mOnce; - } - -private: - bool mOnce; - CALLABLE mCallable; -}; - -template -LLEventTimer* LLEventTimer::run_every(F32 period, const CALLABLE& callable) -{ - // return false to schedule recurring calls - return new Generic(period, false, callable); -} - -template -LLEventTimer* LLEventTimer::run_at(const LLDate& time, const CALLABLE& callable) -{ - // return true for one-shot callback - return new Generic(time, true, callable); -} - -template -LLEventTimer* LLEventTimer::run_after(F32 interval, const CALLABLE& callable) -{ - // one-shot callback after specified interval - return new Generic(interval, true, callable); -} - #endif //LL_EVENTTIMER_H diff --git a/indra/llcommon/llrun.h b/indra/llcommon/llrun.h index d610f86234..ebad5f3eaa 100644 --- a/indra/llcommon/llrun.h +++ b/indra/llcommon/llrun.h @@ -34,6 +34,17 @@ class LLRunnable; +////////////////////////////////////////////////////////////////////////////// +// DEPRECATION WARNING +// LLRunner is one of several mostly redundant ways to schedule future +// callbacks on the main thread. It seems to be unused in the current viewer. +// addRunner() is only called by LLPumpIO::sleepChain(). +// sleepChain() is only called by LLIOSleeper and LLIOSleep. +// LLIOSleeper is referenced only by tests. +// LLIOSleep is only called by LLDeferredChain. +// LLDeferredChain isn't referenced at all. +////////////////////////////////////////////////////////////////////////////// + /** * @class LLRunner * @brief This class manages a set of LLRunnable objects. -- cgit v1.2.3 From f24172d23d900bd6f9d10bb648107bbf4a755aaf Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 17 Aug 2023 10:34:40 -0400 Subject: DRTVWR-588: Correct typo in deprecation warning. --- indra/llcommon/llrun.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/llrun.h b/indra/llcommon/llrun.h index ebad5f3eaa..27e4a43a8d 100644 --- a/indra/llcommon/llrun.h +++ b/indra/llcommon/llrun.h @@ -38,7 +38,7 @@ class LLRunnable; // DEPRECATION WARNING // LLRunner is one of several mostly redundant ways to schedule future // callbacks on the main thread. It seems to be unused in the current viewer. -// addRunner() is only called by LLPumpIO::sleepChain(). +// addRunnable() is only called by LLPumpIO::sleepChain(). // sleepChain() is only called by LLIOSleeper and LLIOSleep. // LLIOSleeper is referenced only by tests. // LLIOSleep is only called by LLDeferredChain. -- cgit v1.2.3 From 931a2fd63de2de417adbe3e02d55af7a459bca36 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 17 Aug 2023 11:23:02 -0400 Subject: DRTVWR-588: print(file=) to binary file still requires str argument. Use f.writelines((bytes, b'\n')) instead. --- indra/llcommon/tests/llsdserialize_test.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index f46682e2d7..efd7dc9852 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -1867,10 +1867,9 @@ namespace tut "lines.''',\n" "]\n" // Don't forget raw-string syntax for Windows pathnames. - // N.B. Using 'print' implicitly adds newlines. "with open(r'" << file.getName() << "', 'wb') as f:\n" " for item in DATA:\n" - " print(llsd.format_notation(item), file=f)\n"; }); + " f.writelines((llsd.format_notation(item), b'\n'))\n"; }); std::ifstream inf(file.getName().c_str()); LLSD item; -- cgit v1.2.3 From 36ed0e98ea7bd591a798a4373e8aa78a8fca4d14 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 17 Aug 2023 11:42:09 -0400 Subject: DRTVWR-588: Try harder to normalize Windows pathames to compare. --- indra/llcommon/tests/llprocess_test.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index c48d913069..56dc2e4fa9 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -260,6 +260,7 @@ public: } std::string getName() const { return mPath.string(); } + std::string getNormalName() const { return mPath.lexically_normal().string(); } private: boost::filesystem::path mPath; @@ -591,7 +592,7 @@ namespace tut " f.write(os.path.normcase(os.path.normpath(os.getcwd())))\n"); // Before running, call setWorkingDirectory() py.mParams.cwd = tempdir.getName(); - std::string expected{ tempdir.getName() }; + std::string expected{ tempdir.getNormalName() }; #if LL_WINDOWS // SIGH, don't get tripped up by "C:" != "c:" -- // but on the Mac, using tolower() fails because "/users" != "/Users"! -- cgit v1.2.3 From b5fe9c476943807aa7526f67dd648b5ad250824b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 17 Aug 2023 11:45:34 -0400 Subject: DRTVWR-588: To write b'\n' in Python source, use "b'\\n'" --- indra/llcommon/tests/llsdserialize_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index efd7dc9852..bb469f0686 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -1869,7 +1869,7 @@ namespace tut // Don't forget raw-string syntax for Windows pathnames. "with open(r'" << file.getName() << "', 'wb') as f:\n" " for item in DATA:\n" - " f.writelines((llsd.format_notation(item), b'\n'))\n"; }); + " f.writelines((llsd.format_notation(item), b'\\n'))\n"; }); std::ifstream inf(file.getName().c_str()); LLSD item; -- cgit v1.2.3 From dc8f2ae2ba1a1348f86f412df7f769e6cc2fe541 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 17 Aug 2023 17:01:56 -0400 Subject: DRTVWR-588: Try even harder to normalize Windows pathnames (SIGHH) --- indra/llcommon/tests/llprocess_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 56dc2e4fa9..ece567f40e 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -260,7 +260,7 @@ public: } std::string getName() const { return mPath.string(); } - std::string getNormalName() const { return mPath.lexically_normal().string(); } + std::string getNormalName() const { return mPath.lexically_normal().make_preferred().string(); } private: boost::filesystem::path mPath; -- cgit v1.2.3 From c8711f4ea5d941d12ae7e6cf99302bfc3211dd3c Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Tue, 5 Sep 2023 22:22:56 +0300 Subject: Initial prototype of embedded LUA --- indra/cmake/CMakeLists.txt | 1 + indra/cmake/Copy3rdPartyLibs.cmake | 1 + indra/cmake/Lualibs.cmake | 15 ++ indra/llcommon/CMakeLists.txt | 1 + indra/newview/CMakeLists.txt | 6 + indra/newview/llfilepicker.cpp | 5 + indra/newview/llfilepicker.h | 3 +- indra/newview/llfloaterluadebug.cpp | 92 +++++++++++++ indra/newview/llfloaterluadebug.h | 62 +++++++++ indra/newview/llluamanager.cpp | 152 +++++++++++++++++++++ indra/newview/llluamanager.h | 38 ++++++ indra/newview/llviewerfloaterreg.cpp | 3 + .../skins/default/xui/en/floater_lua_debug.xml | 98 +++++++++++++ indra/newview/skins/default/xui/en/menu_viewer.xml | 11 ++ indra/newview/viewer_manifest.py | 3 + 15 files changed, 490 insertions(+), 1 deletion(-) create mode 100644 indra/cmake/Lualibs.cmake create mode 100644 indra/newview/llfloaterluadebug.cpp create mode 100644 indra/newview/llfloaterluadebug.h create mode 100644 indra/newview/llluamanager.cpp create mode 100644 indra/newview/llluamanager.h create mode 100644 indra/newview/skins/default/xui/en/floater_lua_debug.xml (limited to 'indra') diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index f0b35c08f3..af01666875 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -43,6 +43,7 @@ set(cmake_SOURCE_FILES LLTestCommand.cmake LLWindow.cmake Linking.cmake + Lualibs.cmake Meshoptimizer.cmake NDOF.cmake OPENAL.cmake diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index d43cc30706..8ae1b5dde5 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -61,6 +61,7 @@ if(WINDOWS) nghttp2.dll libhunspell.dll uriparser.dll + lua54.dll ) # OpenSSL diff --git a/indra/cmake/Lualibs.cmake b/indra/cmake/Lualibs.cmake new file mode 100644 index 0000000000..ec40d0f41c --- /dev/null +++ b/indra/cmake/Lualibs.cmake @@ -0,0 +1,15 @@ +# -*- cmake -*- + +include_guard() + +include(Prebuilt) + +add_library( ll::lualibs INTERFACE IMPORTED ) + +use_system_binary( lualibs ) + +use_prebuilt_binary(lualibs) + +target_link_libraries(ll::lualibs INTERFACE ${lualibs}) + +target_include_directories( ll::lualibs SYSTEM INTERFACE ${LIBS_PREBUILT_DIR}/include/lualibs) diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index ef4899978e..5dd4321330 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -13,6 +13,7 @@ include(Copy3rdPartyLibs) include(ZLIBNG) include(URIPARSER) include(Tracy) +include(lualibs) set(llcommon_SOURCE_FILES diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index dbd1f1b4ac..ca0b7692c4 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -46,6 +46,7 @@ include(VisualLeakDetector) include(ZLIBNG) include(URIPARSER) include(LLPrimitive) +include(lualibs) if (NOT HAVOK_TPV) # When using HAVOK_TPV, the library is precompiled, so no need for this @@ -235,6 +236,7 @@ set(viewer_SOURCE_FILES llfloaterlandholdings.cpp llfloaterlinkreplace.cpp llfloaterloadprefpreset.cpp + llfloaterluadebug.cpp llfloatermarketplacelistings.cpp llfloatermap.cpp llfloatermediasettings.cpp @@ -361,6 +363,7 @@ set(viewer_SOURCE_FILES lllogchat.cpp llloginhandler.cpp lllogininstance.cpp + llluamanager.cpp llmachineid.cpp llmanip.cpp llmaniprotate.cpp @@ -879,6 +882,7 @@ set(viewer_HEADER_FILES llfloaterlandholdings.h llfloaterlinkreplace.h llfloaterloadprefpreset.h + llfloaterluadebug.h llfloatermap.h llfloatermarketplacelistings.h llfloatermediasettings.h @@ -1003,6 +1007,7 @@ set(viewer_HEADER_FILES lllogchat.h llloginhandler.h lllogininstance.h + llluamanager.h llmachineid.h llmanip.h llmaniprotate.h @@ -1698,6 +1703,7 @@ if (WINDOWS) ${SHARED_LIB_STAGING_DIR}/openjp2.dll ${SHARED_LIB_STAGING_DIR}/libhunspell.dll ${SHARED_LIB_STAGING_DIR}/uriparser.dll + ${SHARED_LIB_STAGING_DIR}/lua54.dll #${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/SLVoice.exe #${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/libsndfile-1.dll #${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/vivoxoal.dll diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index f1f156c2e0..b9435dc9df 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -61,6 +61,7 @@ LLFilePicker LLFilePicker::sInstance; #define MODEL_FILTER L"Model files (*.dae)\0*.dae\0" #define SCRIPT_FILTER L"Script files (*.lsl)\0*.lsl\0" #define DICTIONARY_FILTER L"Dictionary files (*.dic; *.xcu)\0*.dic;*.xcu\0" +#define LUA_FILTER L"Script files (*.lua)\0*.lua\0" #endif #ifdef LL_DARWIN @@ -218,6 +219,10 @@ BOOL LLFilePicker::setupFilter(ELoadFilter filter) mOFN.lpstrFilter = DICTIONARY_FILTER \ L"\0"; break; + case FFLOAD_LUA: + mOFN.lpstrFilter = LUA_FILTER \ + L"\0"; + break; default: res = FALSE; break; diff --git a/indra/newview/llfilepicker.h b/indra/newview/llfilepicker.h index 692b908fff..cb453ea87e 100644 --- a/indra/newview/llfilepicker.h +++ b/indra/newview/llfilepicker.h @@ -85,7 +85,8 @@ public: FFLOAD_SCRIPT = 11, FFLOAD_DICTIONARY = 12, FFLOAD_DIRECTORY = 13, // To call from lldirpicker. - FFLOAD_EXE = 14 // Note: EXE will be treated as ALL on Windows and Linux but not on Darwin + FFLOAD_EXE = 14, // Note: EXE will be treated as ALL on Windows and Linux but not on Darwin + FFLOAD_LUA = 15 }; enum ESaveFilter diff --git a/indra/newview/llfloaterluadebug.cpp b/indra/newview/llfloaterluadebug.cpp new file mode 100644 index 0000000000..c8b330f732 --- /dev/null +++ b/indra/newview/llfloaterluadebug.cpp @@ -0,0 +1,92 @@ +/** + * @file llfloaterluadebug.cpp + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterluadebug.h" + +#include "lllineeditor.h" +#include "lltexteditor.h" +#include "llviewermenufile.h" // LLFilePickerReplyThread + +#include "llagent.h" +#include "llappearancemgr.h" +#include "llfloaterreg.h" +#include "llfloaterimnearbychat.h" + +#include "llluamanager.h" + +#if LL_WINDOWS +#pragma comment(lib, "liblua54.a") +#endif + + +LLFloaterLUADebug::LLFloaterLUADebug(const LLSD &key) + : LLFloater(key) +{ +} + + +BOOL LLFloaterLUADebug::postBuild() +{ + mResultOutput = getChild("result_text"); + mLineInput = getChild("lua_cmd"); + mScriptPath = getChild("script_path"); + + getChild("execute_btn")->setClickedCallback(boost::bind(&LLFloaterLUADebug::onExecuteClicked, this)); + getChild("browse_btn")->setClickedCallback(boost::bind(&LLFloaterLUADebug::onBtnBrowse, this)); + +#if !LL_WINDOWS + getChild("execute_btn")->setEnabled(false); + getChild("browse_btn")->setEnabled(false); +#endif + + return TRUE; +} + +LLFloaterLUADebug::~LLFloaterLUADebug() +{} + +void LLFloaterLUADebug::onExecuteClicked() +{ + std::string cmd = mLineInput->getText(); + LLLUAmanager::runScriptLine(cmd); +} + +void LLFloaterLUADebug::onBtnBrowse() +{ + (new LLFilePickerReplyThread(boost::bind(&LLFloaterLUADebug::runSelectedScript, this, _1), LLFilePicker::FFLOAD_LUA, false))->getFile(); +} + +void LLFloaterLUADebug::runSelectedScript(const std::vector &filenames) +{ + std::string filepath = filenames[0]; + if (!filepath.empty()) + { + mScriptPath->setText(filepath); + LLLUAmanager::runScriptFile(filepath); + } +} + diff --git a/indra/newview/llfloaterluadebug.h b/indra/newview/llfloaterluadebug.h new file mode 100644 index 0000000000..a7a3b695d4 --- /dev/null +++ b/indra/newview/llfloaterluadebug.h @@ -0,0 +1,62 @@ +/** + * @file llfloaterluadebug.h + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERLUADEBUG_H +#define LL_LLFLOATERLUADEBUG_H + +#include "llfloater.h" + +extern "C" +{ +#include "lua/lua.h" +#include "lua/lauxlib.h" +#include "lua/lualib.h" +} + +class LLLineEditor; +class LLTextEditor; + +class LLFloaterLUADebug : + public LLFloater +{ + public: + LLFloaterLUADebug(const LLSD& key); + virtual ~LLFloaterLUADebug(); + + /*virtual*/ BOOL postBuild(); + + void onExecuteClicked(); + void onBtnBrowse(); + + void runSelectedScript(const std::vector &filenames); + +private: + LLTextEditor* mResultOutput; + LLLineEditor* mLineInput; + LLLineEditor* mScriptPath; +}; + +#endif // LL_LLFLOATERLUADEBUG_H + diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp new file mode 100644 index 0000000000..516993c967 --- /dev/null +++ b/indra/newview/llluamanager.cpp @@ -0,0 +1,152 @@ +/** + * @file llluamanager.cpp + * @brief classes and functions for interfacing with LUA. + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + * + */ + +#include "llviewerprecompiledheaders.h" +#include "llluamanager.h" + +#include "llagent.h" +#include "llappearancemgr.h" +#include "llfloaterreg.h" +#include "llfloaterimnearbychat.h" + +extern "C" +{ +#include "lua/lua.h" +#include "lua/lauxlib.h" +#include "lua/lualib.h" +} + +#if LL_WINDOWS +#pragma comment(lib, "liblua54.a") +#endif + +int lua_printWarning(lua_State *L) +{ + std::string msg(lua_tostring(L, 1)); + + LL_WARNS() << msg << LL_ENDL; + return 1; +} + +int lua_avatar_sit(lua_State *L) +{ + gAgent.sitDown(); + return 1; +} + +int lua_avatar_stand(lua_State *L) +{ + gAgent.standUp(); + return 1; +} + +int lua_nearby_chat_send(lua_State *L) +{ + std::string msg(lua_tostring(L, 1)); + LLFloaterIMNearbyChat *nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); + nearby_chat->sendChatFromViewer(msg, CHAT_TYPE_NORMAL, gSavedSettings.getBOOL("PlayChatAnim")); + + return 1; +} + +int lua_wear_by_name(lua_State *L) +{ + std::string folder_name(lua_tostring(L, 1)); + LLAppearanceMgr::instance().wearOutfitByName(folder_name); + + return 1; +} + +bool checkLua(lua_State *L, int r) +{ + if (r != LUA_OK) + { + std::string lua_error = lua_tostring(L, -1); + + LL_WARNS() << lua_error << LL_ENDL; + return false; + } + return true; +} + +void initLUA(lua_State *L) +{ + lua_register(L, "lua_printWarning", lua_printWarning); + + lua_register(L, "lua_avatar_sit", lua_avatar_sit); + lua_register(L, "lua_avatar_stand", lua_avatar_stand); + + lua_register(L, "lua_nearby_chat_send", lua_nearby_chat_send); + lua_register(L, "lua_wear_by_name", lua_wear_by_name); +} + +void LLLUAmanager::runScriptFile(const std::string &filename) +{ + LLCoros::instance().launch("LUAScriptFileCoro", [filename]() + { + lua_State *L = luaL_newstate(); + luaL_openlibs(L); + initLUA(L); + + auto LUA_sleep_func = [](lua_State *L) + { + F32 seconds = lua_tonumber(L, -1); + llcoro::suspendUntilTimeout(seconds); + return 0; + }; + + lua_register(L, "sleep", LUA_sleep_func); + + if (checkLua(L, luaL_dofile(L, filename.c_str()))) + { + lua_getglobal(L, "call_once_func"); + if (lua_isfunction(L, -1)) + { + if (checkLua(L, lua_pcall(L, 0, 0, 0))) {} + } + } + + lua_close(L); + }); +} + +void LLLUAmanager::runScriptLine(const std::string &cmd) +{ + LLCoros::instance().launch("LUAScriptFileCoro", [cmd]() + { + lua_State *L = luaL_newstate(); + luaL_openlibs(L); + initLUA(L); + int r = luaL_dostring(L, cmd.c_str()); + if (r != LUA_OK) + { + std::string lua_error = lua_tostring(L, -1); + LL_WARNS() << lua_error << LL_ENDL; + } + lua_close(L); + }); +} diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h new file mode 100644 index 0000000000..d31f159480 --- /dev/null +++ b/indra/newview/llluamanager.h @@ -0,0 +1,38 @@ +/** + * @file llluamanager.h + * @brief classes and functions for interfacing with LUA. + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLLUAMANAGER_H +#define LL_LLLUAMANAGER_H + +class LLLUAmanager +{ +public: + static void runScriptFile(const std::string &filename); + static void runScriptLine(const std::string &cmd); +}; + + +#endif diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 0f2fe1e1cd..d00ccd05a0 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -90,6 +90,7 @@ #include "llfloaterlandholdings.h" #include "llfloaterlinkreplace.h" #include "llfloaterloadprefpreset.h" +#include "llfloaterluadebug.h" #include "llfloatermap.h" #include "llfloatermarketplacelistings.h" #include "llfloatermediasettings.h" @@ -385,6 +386,8 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("land_holdings", "floater_land_holdings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("linkreplace", "floater_linkreplace.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("load_pref_preset", "floater_load_pref_preset.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + + LLFloaterReg::add("lua_debug", "floater_lua_debug.xml", (LLFloaterBuildFunc) &LLFloaterReg::build); LLFloaterReg::add("mem_leaking", "floater_mem_leaking.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); diff --git a/indra/newview/skins/default/xui/en/floater_lua_debug.xml b/indra/newview/skins/default/xui/en/floater_lua_debug.xml new file mode 100644 index 0000000000..7ed6c35151 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_lua_debug.xml @@ -0,0 +1,98 @@ + + + + LUA string: + + + + + Select title + + + + + + + + Double click me + + + diff --git a/indra/newview/scripts/lua/luafloater_gesture_list.xml b/indra/newview/scripts/lua/luafloater_gesture_list.xml new file mode 100644 index 0000000000..a38a04eed0 --- /dev/null +++ b/indra/newview/scripts/lua/luafloater_gesture_list.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/indra/newview/scripts/lua/test_luafloater_demo.lua b/indra/newview/scripts/lua/test_luafloater_demo.lua new file mode 100644 index 0000000000..a7bbdccb30 --- /dev/null +++ b/indra/newview/scripts/lua/test_luafloater_demo.lua @@ -0,0 +1,69 @@ +XML_FILE_PATH = "luafloater_demo.xml" + +leap = require 'leap' +coro = require 'coro' + +--event pump for sending actions to the floater +COMMAND_PUMP_NAME = "" +--table of floater UI events +e={} +coro.launch(function () + e = leap.request("LLFloaterReg", {op="getFloaterEvents"})["events"] + leap.done() +end) +leap.process() + +function post(action) + leap.send(COMMAND_PUMP_NAME, action) +end + +function getCurrentTime() + local currentTime = os.date("*t") + return string.format("%02d:%02d:%02d", currentTime.hour, currentTime.min, currentTime.sec) +end + +function handleEvents(event_data) + if event_data.event == e.COMMIT_EVENT then + if event_data.ctrl_name == "disable_ctrl" then + post({action="set_enabled", ctrl_name="open_btn", value = (1 - event_data.value)}) + elseif event_data.ctrl_name == "title_cmb" then + post({action="set_title", value= event_data.value}) + elseif event_data.ctrl_name == "open_btn" then + floater_name = leap.request(COMMAND_PUMP_NAME, {action="get_value", ctrl_name='openfloater_cmd'})['value'] + leap.send("LLFloaterReg", {name = floater_name, op = "showInstance"}) + end + elseif event_data.event == e.DOUBLE_CLICK_EVENT then + if event_data.ctrl_name == "show_time_lbl" then + post({action="set_value", ctrl_name="time_lbl", value= getCurrentTime()}) + end + elseif event_data.event == e.CLOSE_EVENT then + print_warning("Floater was closed") + leap.done() + --script received event pump name, after floater was built + elseif event_data.event == e.POST_BUILD_EVENT then + COMMAND_PUMP_NAME = event_data.command_name + end +end + +catch_events = leap.WaitFor:new(-1, "all_events") +function catch_events:filter(pump, data) + return data +end + +function process_events(waitfor) + event_data = waitfor:wait() + while event_data do + handleEvents(event_data) + event_data = waitfor:wait() + end +end + +local key = {xml_path = XML_FILE_PATH, op = "showLuaFloater"} + +--sign for additional events for defined control {= {action1, action2, ...}} +key.extra_events={show_time_lbl = {e.RIGHT_MOUSE_DOWN_EVENT, e.DOUBLE_CLICK_EVENT}} +leap.send("LLFloaterReg", key) + +coro.launch(process_events, catch_events) +leap.process() +print_warning("End of the script") diff --git a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua new file mode 100644 index 0000000000..070ff8415a --- /dev/null +++ b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua @@ -0,0 +1,62 @@ +XML_FILE_PATH = "luafloater_gesture_list.xml" + +leap = require 'leap' +coro = require 'coro' +LLGesture = require 'LLGesture' + +--event pump for sending actions to the floater +COMMAND_PUMP_NAME = "" +--table of floater UI events +e={} +coro.launch(function () + e = leap.request("LLFloaterReg", {op="getFloaterEvents"})["events"] + leap.done() +end) +leap.process() + +function post(action) + leap.send(COMMAND_PUMP_NAME, action) +end + +function handleEvents(event_data) + if event_data.event == e.CLOSE_EVENT then + leap.done() + elseif event_data.event == e.POST_BUILD_EVENT then + COMMAND_PUMP_NAME = event_data.command_name + gestures_uuid = LLGesture.getActiveGestures() + local action_data = {} + action_data.action = "add_list_element" + action_data.ctrl_name = "gesture_list" + gestures = {} + for uuid, info in pairs(gestures_uuid) do + element={value = uuid, columns ={column = "gesture_name", value = info.name}} + action_data.value = element + post(action_data) + end + elseif event_data.event == e.DOUBLE_CLICK_EVENT then + if event_data.ctrl_name == "gesture_list" then + LLGesture.startGesture(event_data.value) + end + end +end + +catch_events = leap.WaitFor:new(-1, "all_events") +function catch_events:filter(pump, data) + return data +end + +function process_events(waitfor) + event_data = waitfor:wait() + while event_data do + handleEvents(event_data) + event_data = waitfor:wait() + end +end + +local key = {xml_path = XML_FILE_PATH, op = "showLuaFloater"} +--receive additional events for defined control {= {action1, action2, ...}} +key.extra_events={gesture_list = {e.DOUBLE_CLICK_EVENT}} +leap.send("LLFloaterReg", key) + +coro.launch(process_events, catch_events) +leap.process() -- cgit v1.2.3 From ba6784647b53919c09ef339fd99af152aa0f8458 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Wed, 20 Mar 2024 23:21:48 +0200 Subject: LLLuaFloater code clean up --- indra/llui/llfloater.cpp | 4 --- indra/llui/llfloater.h | 3 -- indra/llui/llfloaterreglistener.cpp | 20 ++--------- indra/llui/llfloaterreglistener.h | 1 - indra/llui/llluafloater.cpp | 39 +++++++++------------- indra/llui/llluafloater.h | 3 +- indra/newview/scripts/lua/test_luafloater_demo.lua | 2 +- .../scripts/lua/test_luafloater_gesture_list.lua | 2 +- 8 files changed, 23 insertions(+), 51 deletions(-) (limited to 'indra') diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index b9b69b5a33..de3de53569 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -3623,10 +3623,6 @@ void LLFloater::applyRelativePosition() translate(new_center.mX - cur_center.mX, new_center.mY - cur_center.mY); } -bool LLFloater::isDefaultBtnName(const std::string& name) -{ - return (std::find(std::begin(sButtonNames), std::end(sButtonNames), name) != std::end(sButtonNames)); -} LLCoordFloater::LLCoordFloater(F32 x, F32 y, LLFloater& floater) : coord_t((S32)x, (S32)y) diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h index 9d49c4538e..88f9e77777 100644 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -372,9 +372,6 @@ public: void enableResizeCtrls(bool enable, bool width = true, bool height = true); bool isPositioning(LLFloaterEnums::EOpenPositioning p) const { return (p == mPositioning); } - - static bool isDefaultBtnName(const std::string& name); - protected: void applyControlsAndPosition(LLFloater* other); diff --git a/indra/llui/llfloaterreglistener.cpp b/indra/llui/llfloaterreglistener.cpp index bdf26c1db8..8316101264 100644 --- a/indra/llui/llfloaterreglistener.cpp +++ b/indra/llui/llfloaterreglistener.cpp @@ -74,11 +74,9 @@ LLFloaterRegListener::LLFloaterRegListener(): &LLFloaterRegListener::clickButton, requiredNameButton); - LLSD requiredParams; - requiredParams["xml_path"] = LLSD(); add("showLuaFloater", - "Open the new floater using XML file specified in [\"xml_path\"]", - &LLFloaterRegListener::showLuaFloater, requiredParams); + "Open the new floater using XML file specified in [\"xml_path\"] with ID in [\"reqid\"]", + &LLLuaFloater::showLuaFloater, {llsd::map("xml_path", LLSD(), "reqid", LLSD())}); add("getFloaterEvents", "Return the table of Lua Floater events which are send to the script", &LLFloaterRegListener::getLuaFloaterEvents); @@ -165,20 +163,8 @@ void LLFloaterRegListener::clickButton(const LLSD& event) const } } -void LLFloaterRegListener::showLuaFloater(const LLSD &event) const -{ - LLLuaFloater::showLuaFloater(event); -} - void LLFloaterRegListener::getLuaFloaterEvents(const LLSD &event) const { - if (event.has("reply")) - { - LLSD events_data = LLLuaFloater::getEventsData(); - - LLReqID reqID(event); - LLSD reply(reqID.makeResponse()); - LLEventPumps::instance().obtain(event["reply"]).post(reply.with("events", events_data)); - } + Response response(llsd::map("events", LLLuaFloater::getEventsData()), event); } diff --git a/indra/llui/llfloaterreglistener.h b/indra/llui/llfloaterreglistener.h index cbfb7855b9..9cb0af2de5 100644 --- a/indra/llui/llfloaterreglistener.h +++ b/indra/llui/llfloaterreglistener.h @@ -50,7 +50,6 @@ private: void instanceVisible(const LLSD& event) const; void clickButton(const LLSD& event) const; - void showLuaFloater(const LLSD &event) const; void getLuaFloaterEvents(const LLSD &event) const; }; diff --git a/indra/llui/llluafloater.cpp b/indra/llui/llluafloater.cpp index 27ae85c844..668c0edc53 100644 --- a/indra/llui/llluafloater.cpp +++ b/indra/llui/llluafloater.cpp @@ -51,8 +51,9 @@ std::map EVENT_LIST = LLLuaFloater::LLLuaFloater(const LLSD &key) : LLFloater(key), - mCommandPumpName(key["reply"].asString()), - mDispatcher("LLLuaFloater", "action") + mReplyPumpName(key["reply"].asString()), + mDispatcher("LLLuaFloater", "action"), + mReqID(key) { mListenerPumpName = LLUUID::generateNewID().asString(); @@ -69,7 +70,7 @@ LLLuaFloater::LLLuaFloater(const LLSD &key) : return false; }); - LLSD requiredParams = LLSD().with("ctrl_name", LLSD()).with("value", LLSD()); + LLSD requiredParams = llsd::map("ctrl_name", LLSD(), "value", LLSD()); mDispatcher.add("set_enabled", "", [this](const LLSD &event) { LLUICtrl *ctrl = getChild(event["ctrl_name"].asString()); @@ -103,13 +104,11 @@ LLLuaFloater::LLLuaFloater(const LLSD &key) : } }, requiredParams); - requiredParams = LLSD().with("value", LLSD()); mDispatcher.add("set_title", "", [this](const LLSD &event) { setTitle(event["value"].asString()); - }, requiredParams); + }, llsd::map("value", LLSD())); - requiredParams = LLSD().with("ctrl_name", LLSD()).with("reqid", LLSD()); mDispatcher.add("get_value", "", [this](const LLSD &event) { LLUICtrl *ctrl = getChild(event["ctrl_name"].asString()); @@ -120,7 +119,7 @@ LLLuaFloater::LLLuaFloater(const LLSD &key) : response["reqid"] = event["reqid"]; post(response); } - }, requiredParams); + }, llsd::map("ctrl_name", LLSD(), "reqid", LLSD())); } LLLuaFloater::~LLLuaFloater() @@ -131,13 +130,8 @@ LLLuaFloater::~LLLuaFloater() BOOL LLLuaFloater::postBuild() { - child_list_t::const_iterator iter = getChildList()->begin(); - child_list_t::const_iterator end = getChildList()->end(); - for (; iter != end; ++iter) + for (LLView *view : *getChildList()) { - LLView *view = *iter; - if (!view) continue; - LLUICtrl *ctrl = dynamic_cast(view); if (ctrl) { @@ -158,27 +152,24 @@ BOOL LLLuaFloater::postBuild() if (mKey.has("extra_events")) { //the first value is ctrl name, the second contains array of events to send - const LLSD &events_map = mKey["extra_events"]; - for (LLSD::map_const_iterator it = events_map.beginMap(); it != events_map.endMap(); ++it) + for (const auto &[name, data] : llsd::inMap(mKey["extra_events"])) { - std::string name = (*it).first; - LLSD data = (*it).second; - for (LLSD::array_const_iterator events_it = data.beginArray(); events_it != data.endArray(); ++events_it) + for (const auto &event : llsd::inArray(data)) { - registerCallback(name, events_it->asString()); + registerCallback(name, event); } } } //send pump name to the script after the floater is built - post(LLSD().with("command_name", mListenerPumpName).with("event", EVENT_LIST["POST_BUILD_EVENT"])); + post(llsd::map("command_name", mListenerPumpName, "event", EVENT_LIST["POST_BUILD_EVENT"])); return true; } void LLLuaFloater::onClose(bool app_quitting) { - post(LLSD().with("event", EVENT_LIST["CLOSE_EVENT"])); + post(llsd::map("event", EVENT_LIST["CLOSE_EVENT"], "app_quitting", app_quitting)); } void LLLuaFloater::registerCallback(const std::string &ctrl_name, const std::string &event) @@ -247,8 +238,10 @@ void LLLuaFloater::registerCallback(const std::string &ctrl_name, const std::str void LLLuaFloater::post(const LLSD &data) { - //send event data to the script - LLEventPumps::instance().obtain(mCommandPumpName).post(data); + // send event data to the script signed with ["reqid"] key + LLSD stamped_data(data); + mReqID.stamp(stamped_data); + LLEventPumps::instance().obtain(mReplyPumpName).post(stamped_data); } void LLLuaFloater::showLuaFloater(const LLSD &data) diff --git a/indra/llui/llluafloater.h b/indra/llui/llluafloater.h index 0dd39e7d1e..b9f96f0ad3 100644 --- a/indra/llui/llluafloater.h +++ b/indra/llui/llluafloater.h @@ -45,10 +45,11 @@ public: static LLSD getEventsData(); private: + LLReqID mReqID; LLEventDispatcher mDispatcher; LLTempBoundListener mBoundListener; std::string mListenerPumpName; - std::string mCommandPumpName; + std::string mReplyPumpName; }; #endif diff --git a/indra/newview/scripts/lua/test_luafloater_demo.lua b/indra/newview/scripts/lua/test_luafloater_demo.lua index a7bbdccb30..2cbafcec14 100644 --- a/indra/newview/scripts/lua/test_luafloater_demo.lua +++ b/indra/newview/scripts/lua/test_luafloater_demo.lua @@ -62,7 +62,7 @@ local key = {xml_path = XML_FILE_PATH, op = "showLuaFloater"} --sign for additional events for defined control {= {action1, action2, ...}} key.extra_events={show_time_lbl = {e.RIGHT_MOUSE_DOWN_EVENT, e.DOUBLE_CLICK_EVENT}} -leap.send("LLFloaterReg", key) +leap.send("LLFloaterReg", key, "floater1") coro.launch(process_events, catch_events) leap.process() diff --git a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua index 070ff8415a..5ea2b1e30d 100644 --- a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua +++ b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua @@ -56,7 +56,7 @@ end local key = {xml_path = XML_FILE_PATH, op = "showLuaFloater"} --receive additional events for defined control {= {action1, action2, ...}} key.extra_events={gesture_list = {e.DOUBLE_CLICK_EVENT}} -leap.send("LLFloaterReg", key) +leap.send("LLFloaterReg", key, "floater1") coro.launch(process_events, catch_events) leap.process() -- cgit v1.2.3 From 0566af988790e95414ed18cd82206710094d8fae Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 21 Mar 2024 23:56:46 +0900 Subject: WIP: Add fiber.lua module and use in leap.lua and WaitQueue.lua. fiber.lua goes beyond coro.lua in that it distinguishes ready suspended coroutines from waiting suspended coroutines, and presents a rudimentary scheduler in fiber.yield(). yield() can determine that when all coroutines are waiting, it's time to retrieve the next incoming event from the viewer. Moreover, it can detect when all coroutines have completed and exit without being explicitly told. fiber.launch() associates a name with each fiber for debugging purposes. fiber.get_name() retrieves the name of the specified fiber, or the running fiber. fiber.status() is like coroutine.status(), but can return 'ready' or 'waiting' instead of 'suspended'. fiber.yield() leaves the calling fiber ready, but lets other ready fibers run. fiber.wait() suspends the calling fiber and lets other ready fibers run. fiber.wake(), called from some other coroutine, returns the passed fiber to ready status for a future call to fiber.yield(). fiber.run() drives the scheduler to run all fibers to completion. If, on completion of the subject Lua script, LuaState::expr() detects that the script loaded fiber.lua, it calls fiber.run() to finish running any dangling fibers. This lets a script make calls to fiber.launch() and then just fall off the end, leaving the implicit fiber.run() call to run them all. fiber.lua is designed to allow the main thread, as well as explicitly launched coroutines, to make leap.request() calls. This part still needs debugging. The leap.lua module now configures a fiber.set_idle() function that honors leap.done(), but calls get_event_next() and dispatches the next incoming event. leap.request() and generate() now leave the reqid stamp in the response. This lets a caller handle subsequent events with the same reqid, e.g. for LLLuaFloater. Remove leap.process(): it has been superseded by fiber.run(). Remove leap.WaitFor:iterate(): unfortunately that would run afoul of the Luau bug that prevents suspending the calling coroutine within a generic 'for' iterator function. Make leap.lua use weak tables to track WaitFor objects. Make WaitQueue:Dequeue() call fiber.wait() to suspend its caller when the queue is empty, and Enqueue() call fiber.wake() to set it ready again when a new item is pushed. Make llluamanager_test.cpp's leap test script use the fiber module to launch coroutines, instead of the coro module. Fix a bug in which its drain() function was inadvertently setting and testing the global 'item' variable instead of one local to the function. Since some other modules had the same bug, it was getting confused. Also add printf.lua, providing a printf() function. printf() is short for print(string.format()), but it can also print tables: anything not a number or string is formatted using the inspect() function. Clean up some LL_DEBUGS() output left over from debugging lua_tollsd(). --- indra/llcommon/lua_function.cpp | 128 ++++++++----- indra/newview/scripts/lua/WaitQueue.lua | 29 ++- indra/newview/scripts/lua/fiber.lua | 301 ++++++++++++++++++++++++++++++ indra/newview/scripts/lua/leap.lua | 195 ++++++++++--------- indra/newview/scripts/lua/printf.lua | 19 ++ indra/newview/tests/llluamanager_test.cpp | 34 ++-- 6 files changed, 531 insertions(+), 175 deletions(-) create mode 100644 indra/newview/scripts/lua/fiber.lua create mode 100644 indra/newview/scripts/lua/printf.lua (limited to 'indra') 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 #include +#include #include // std::quoted #include #include // 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 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 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; } diff --git a/indra/newview/scripts/lua/WaitQueue.lua b/indra/newview/scripts/lua/WaitQueue.lua index 00766ccae7..b15e9c443b 100644 --- a/indra/newview/scripts/lua/WaitQueue.lua +++ b/indra/newview/scripts/lua/WaitQueue.lua @@ -2,8 +2,12 @@ -- the Dequeue() operation blocks the calling coroutine until some other -- coroutine Enqueue()s a new value. +local fiber = require('fiber') local Queue = require('Queue') +-- local debug = print_debug +local function debug(...) end + local WaitQueue = Queue:new() function WaitQueue:new() @@ -32,11 +36,9 @@ function WaitQueue:_wake_waiters() -- cases. With multiple consumers, if more than one is trying to -- Dequeue() from an empty WaitQueue, we'll have multiple waiters. -- Unlike OS threads, with cooperative concurrency it doesn't make sense - -- to "notify all": we need resume only one of the waiting Dequeue() - -- callers. But since resuming that caller might entail either Enqueue() - -- or Dequeue() calls, recheck every time around to see if we must resume - -- another waiting coroutine. - while not self:IsEmpty() and #self._waiters > 0 do + -- to "notify all": we need wake only one of the waiting Dequeue() + -- callers. + if not self:IsEmpty() and next(self._waiters) then -- Pop the oldest waiting coroutine instead of the most recent, for -- more-or-less round robin fairness. But skip any coroutines that -- have gone dead in the meantime. @@ -47,11 +49,7 @@ function WaitQueue:_wake_waiters() -- do we still have at least one waiting coroutine? if waiter then -- don't pass the head item: let the resumed coroutine retrieve it - local ok, message = coroutine.resume(waiter) - -- if resuming that waiter encountered an error, don't swallow it - if not ok then - error(message) - end + fiber.wake(waiter) end end end @@ -62,18 +60,17 @@ function WaitQueue:Dequeue() -- the queue while there are still items left, and we want the -- consumer(s) to retrieve those last few items. if self._closed then + debug('WaitQueue:Dequeue(): closed') return nil end - local coro = coroutine.running() - if coro == nil then - error("WaitQueue:Dequeue() trying to suspend main coroutine") - end + debug('WaitQueue:Dequeue(): waiting') -- add the running coroutine to the list of waiters - table.insert(self._waiters, coro) + table.insert(self._waiters, fiber.running()) -- then let somebody else run - coroutine.yield() + fiber.wait() end -- here we're sure this queue isn't empty + debug('WaitQueue:Dequeue() calling Queue.Dequeue()') return Queue.Dequeue(self) end diff --git a/indra/newview/scripts/lua/fiber.lua b/indra/newview/scripts/lua/fiber.lua new file mode 100644 index 0000000000..f18d133cc8 --- /dev/null +++ b/indra/newview/scripts/lua/fiber.lua @@ -0,0 +1,301 @@ +-- Organize Lua coroutines into fibers. + +-- In this usage, the difference between coroutines and fibers is that fibers +-- have a scheduler. Yielding a fiber means allowing other fibers, plural, to +-- run: it's more than just returning control to the specific Lua thread that +-- resumed the running coroutine. + +-- fiber.launch() creates a new fiber ready to run. +-- fiber.status() reports (augmented) status of the passed fiber: instead of +-- 'suspended', it returns either 'ready' or 'waiting' +-- fiber.yield() allows other fibers to run, but leaves the calling fiber +-- ready to run. +-- fiber.wait() marks the running fiber not ready, and resumes other fibers. +-- fiber.wake() marks the designated suspended fiber ready to run, but does +-- not yet resume it. +-- fiber.run() runs all current fibers until all have terminated (successfully +-- or with an error). + +local printf = require 'printf' +-- local debug = printf +local function debug(...) end +local coro = require 'coro' + +local fiber = {} + +-- The tables in which we track fibers must have weak keys so dead fibers +-- can be garbage-collected. +local weak_values = {__mode='v'} +local weak_keys = {__mode='k'} + +-- Track each current fiber as being either ready to run or not ready +-- (waiting). wait() moves the running fiber from ready to waiting; wake() +-- moves the designated fiber from waiting back to ready. +-- The ready table is used as a list so yield() can go round robin. +local ready = setmetatable({'main'}, weak_keys) +-- The waiting table is used as a set because order doesn't matter. +local waiting = setmetatable({}, weak_keys) + +-- Every fiber has a name, for diagnostic purposes. Names must be unique. +-- A colliding name will be suffixed with an integer. +-- Predefine 'main' with our marker so nobody else claims that name. +local names = setmetatable({main='main'}, weak_keys) +local byname = setmetatable({main='main'}, weak_values) +-- each colliding name has its own distinct suffix counter +local suffix = {} + +-- Specify a nullary idle() callback to be called whenever there are no ready +-- fibers but there are waiting fibers. The idle() callback is responsible for +-- changing zero or more waiting fibers to ready fibers by calling +-- fiber.wake(), although a given call may leave them all still waiting. +-- When there are no ready fibers, it's a good idea for the idle() function to +-- return control to a higher-level execution agent. Simply returning without +-- changing any fiber's status will spin the CPU. +-- The idle() callback can return non-nil to exit fiber.run() with that value. +function fiber._idle() + error('fiber.yield(): you must first call set_idle(nullary idle() function)') +end + +function fiber.set_idle(func) + fiber._idle = func +end + +-- Launch a new Lua fiber, ready to run. +function fiber.launch(name, func, ...) + local args = table.pack(...) + local co = coroutine.create(function() func(table.unpack(args)) end) + -- a new fiber is ready to run + table.insert(ready, co) + local namekey = name + while byname[namekey] do + if not suffix[name] then + suffix[name] = 1 + end + suffix[name] += 1 + namekey = name .. tostring(suffix[name]) + end + -- found a namekey not yet in byname: set it + byname[namekey] = co + -- and remember it as this fiber's name + names[co] = namekey +-- debug('launch(%s)', namekey) +-- debug('byname[%s] = %s', namekey, tostring(byname[namekey])) +-- debug('names[%s] = %s', tostring(co), names[co]) +-- debug('ready[-1] = %s', tostring(ready[#ready])) +end + +-- for debugging +function fiber.print_all() + print('Ready fibers:' .. if next(ready) then '' else ' none') + for _, co in pairs(ready) do + printf(' %s: %s', fiber.get_name(co), fiber.status(co)) + end + print('Waiting fibers:' .. if next(waiting) then '' else ' none') + for co in pairs(waiting) do + printf(' %s: %s', fiber.get_name(co), fiber.status(co)) + end +end + +-- return either the running coroutine or, if called from the main thread, +-- 'main' +function fiber.running() + return coroutine.running() or 'main' +end + +-- Query a fiber's name (nil for the running fiber) +function fiber.get_name(co) + if not co then + co = fiber.running() + end + if not names[co] then + return 'unknown' + end + return names[co] +end + +-- Query status of the passed fiber +function fiber.status(co) + local running = coroutine.running() + if (not co) or co == running then + -- silly to ask the status of the running fiber: it's 'running' + return 'running' + end + if co ~= 'main' then + -- for any coroutine but main, consult coroutine.status() + local status = coroutine.status(co) + if status ~= 'suspended' then + return status + end + -- here co is suspended, answer needs further refinement + else + -- co == 'main' + if not running then + -- asking about 'main' from the main fiber + return 'running' + end + -- asking about 'main' from some other fiber, so presumably main is suspended + end + -- here we know co is suspended -- but is it ready to run? + if waiting[co] then + return 'waiting' + end + -- not waiting should imply ready: sanity check + for _, maybe in pairs(ready) do + if maybe == co then + return 'ready' + end + end + -- Calls within yield() between popping the next ready fiber and + -- re-appending it to the list are in this state. Once we're done + -- debugging yield(), we could reinstate either of the below. +-- error(string.format('fiber.status(%s) is stumped', fiber.get_name(co))) +-- print(string.format('*** fiber.status(%s) is stumped', fiber.get_name(co))) + return '(unknown)' +end + +-- change the running fiber's status to waiting +local function set_waiting() + -- if called from the main fiber, inject a 'main' marker into the list + co = fiber.running() + -- delete from ready list + for i, maybe in pairs(ready) do + if maybe == co then + table.remove(ready, i) + break + end + end + -- add to waiting list + waiting[co] = true +end + +-- Suspend the current fiber until some other fiber calls fiber.wake() on it +function fiber.wait() + set_waiting() + -- now yield to other fibers + fiber.yield() +end + +-- Mark a suspended fiber as being ready to run +function fiber.wake(co) + if not waiting[co] then + error(string.format('fiber.wake(%s) but status=%s, ready=%s, waiting=%s', + names[co], fiber.status(co), ready[co], waiting[co])) + end + -- delete from waiting list + waiting[co] = nil + -- add to end of ready list + table.insert(ready, co) + -- but don't yet resume it: that happens next time we reach yield() +end + +-- Run fibers until all but main have terminated: return nil. +-- Or until configured idle() callback returns x ~= nil: return x. +function fiber.run() + -- A fiber calling run() is not also doing other useful work. Tell yield() + -- that we're waiting. Otherwise it would keep seeing that our caller is + -- ready and return to us, instead of realizing that all coroutines are + -- waiting and call idle(). + set_waiting() + local others, idle_done + repeat + debug('%s calling fiber.run() calling yield()', fiber.get_name()) + others, idle_done = fiber.yield() + debug("%s fiber.run()'s yield() returned %s, %s", fiber.get_name(), + tostring(others), tostring(idle_done)) + until (not others) + debug('%s fiber.run() done', fiber.get_name()) + fiber.wake(fiber.running()) + -- Once there are no more waiting fibers, and the only ready fiber is + -- main, return to main. All previously-launched fibers are done. Possibly + -- the chunk is done, or the chunk may decide to launch a new batch of + -- fibers. + return idle_done +end + +-- pop and return the next not-dead fiber in the ready list, or nil if none remain +local function live_ready_iter() + -- don't write + -- for co in table.remove, ready, 1 + -- because it would keep passing a new second parameter! + for co in function() return table.remove(ready, 1) end do + debug('%s live_ready_iter() sees %s, status %s', + fiber.get_name(), fiber.get_name(co), fiber.status(co)) + -- keep removing the head entry until we find one that's not dead, + -- discarding any dead coroutines along the way + if co == 'main' or coroutine.status(co) ~= 'dead' then + debug('%s live_ready_iter() returning %s', + fiber.get_name(), fiber.get_name(co)) + return co + end + end + debug('%s live_ready_iter() returning nil', fiber.get_name()) + return nil +end + +-- prune the set of waiting fibers +local function prune_waiting() + for waiter in pairs(waiting) do + if waiter ~= 'main' and coroutine.status(waiter) == 'dead' then + waiting[waiter] = nil + end + end +end + +-- Give other ready fibers a chance to run, leaving this one ready, returning +-- after a cycle. Returns: +-- * true, nil if there remain other live fibers, whether ready or waiting +-- * false, nil if this is the only remaining fiber +-- * nil, x if configured idle() callback returned non-nil x +function fiber.yield() + if coroutine.running() then + -- seize the opportunity to make sure the viewer isn't shutting down +-- check_stop() + -- this is a real coroutine, yield normally to main or whoever + coroutine.yield() + -- main certainly still exists + return true + end + + -- This is the main fiber: coroutine.yield() doesn't work. + -- Instead, resume each of the ready fibers. + -- Prune the set of waiting fibers after every time fiber business logic + -- runs (i.e. other fibers might have terminated or hit error), such as + -- here on entry. + prune_waiting() + local others, idle_stop + repeat + for co in live_ready_iter do + -- seize the opportunity to make sure the viewer isn't shutting down +-- check_stop() + -- before we re-append co, is it the only remaining entry? + others = next(ready) + -- co is live, re-append it to the ready list + table.insert(ready, co) + if co == 'main' then + -- Since we know the caller is the main fiber, it's our turn. + -- Tell caller if there are other ready or waiting fibers. + return others or next(waiting) + end + -- not main, but some other ready coroutine: + -- use coro.resume() so we'll propagate any error encountered + coro.resume(co) + prune_waiting() + end + -- Here there are no ready fibers. Are there any waiting fibers? + if not next(waiting) then + return false + end + -- there are waiting fibers: call consumer's configured idle() function + idle_stop = fiber._idle() + if idle_stop ~= nil then + return nil, idle_stop + end + prune_waiting() + -- loop "forever", that is, until: + -- * main is ready, or + -- * there are neither ready fibers nor waiting fibers, or + -- * fiber._idle() returned non-nil + until false +end + +return fiber diff --git a/indra/newview/scripts/lua/leap.lua b/indra/newview/scripts/lua/leap.lua index 81728e7230..60e8266a76 100644 --- a/indra/newview/scripts/lua/leap.lua +++ b/indra/newview/scripts/lua/leap.lua @@ -38,7 +38,10 @@ -- leap.process(). process() won't notice until the next event from the -- viewer, though. +local fiber = require('fiber') local ErrorQueue = require('ErrorQueue') +-- local debug = require('printf') +local function debug(...) end local leap = {} @@ -68,11 +71,13 @@ leap._reply, leap._command = get_event_pumps() -- later one. That means that no incoming event will ever be given to -- the old WaitForReqid object. Any coroutine waiting on the discarded -- WaitForReqid object would therefore wait forever. -leap._pending = {} +-- these are weak values tables +local weak_values = {__mode='v'} +leap._pending = setmetatable({}, weak_values) -- Our consumer will instantiate some number of WaitFor subclass objects. -- As these are traversed in descending priority order, we must keep -- them in a list. -leap._waitfors = {} +leap._waitfors = setmetatable({}, weak_values) -- It has been suggested that we should use UUIDs as ["reqid"] values, -- since UUIDs are guaranteed unique. However, as the "namespace" for -- ["reqid"] values is our very own _reply pump, we can get away with @@ -91,15 +96,13 @@ function leap.cmdpump() return leap._command end --- local inspect = require('inspect') - -- Fire and forget. Send the specified request LLSD, expecting no reply. -- In fact, should the request produce an eventual reply, it will be -- treated as an unsolicited event. -- -- See also request(), generate(). function leap.send(pump, data, reqid) --- print_debug('leap.send('..pump..', '..inspect(data)..', '..reqid..') entry') + debug('leap.send(%s, %s, %s) entry', pump, data, reqid) local data = data if type(data) == 'table' then data = table.clone(data) @@ -108,10 +111,26 @@ function leap.send(pump, data, reqid) data['reqid'] = reqid end end --- print_debug('leap.send('..pump..', '..inspect(data)..') calling post_on()') + debug('leap.send(%s, %s) calling post_on()', pump, data) post_on(pump, data) end +-- common setup code shared by request() and generate() +local function requestSetup(pump, data) + -- invent a new, unique reqid + leap._reqid += 1 + local reqid = leap._reqid + -- Instantiate a new WaitForReqid object. The priority is irrelevant + -- because, unlike the WaitFor base class, WaitForReqid does not + -- self-register on our leap._waitfors list. Instead, capture the new + -- WaitForReqid object in leap._pending so dispatch() can find it. + leap._pending[reqid] = leap.WaitForReqid:new(reqid) + -- Pass reqid to send() to stamp it into (a copy of) the request data. + debug('requestSetup(%s, %s)', pump, data) + leap.send(pump, data, reqid) + return reqid +end + -- Send the specified request LLSD, expecting exactly one reply. Block -- the calling coroutine until we receive that reply. -- @@ -131,39 +150,20 @@ end -- -- See also send(), generate(). function leap.request(pump, data) - local reqid = leap._requestSetup(pump, data) + local reqid = requestSetup(pump, data) local waitfor = leap._pending[reqid] --- print_debug('leap.request('..tostring(pump)..', '..inspect(data)..') about to wait on '.. --- tostring(waitfor)) + debug('leap.request(%s, %s) about to wait on %s', pump, data, tostring(waitfor)) local ok, response = pcall(waitfor.wait, waitfor) --- print_debug('leap.request('..tostring(pump)..', '..inspect(data)..') got '.. --- tostring(ok)..': '..inspect(response)) + debug('leap.request(%s, %s) got %s: %s', pump, data, ok, response) -- kill off temporary WaitForReqid object, even if error leap._pending[reqid] = nil if ok then - response.reqid = nil return response else error(response) end end --- common setup code shared by request() and generate() -function leap._requestSetup(pump, data) - -- invent a new, unique reqid - leap._reqid += 1 - local reqid = leap._reqid - -- Instantiate a new WaitForReqid object. The priority is irrelevant - -- because, unlike the WaitFor base class, WaitForReqid does not - -- self-register on our leap._waitfors list. Instead, capture the new - -- WaitForReqid object in leap._pending so _dispatch() can find it. - leap._pending[reqid] = leap.WaitForReqid:new(reqid) - -- Pass reqid to send() to stamp it into (a copy of) the request data. --- print_debug('leap._requestSetup('..tostring(pump)..', '..inspect(data)..')') - leap.send(pump, data, reqid) - return reqid -end - -- Send the specified request LLSD, expecting an arbitrary number of replies. -- Each one is yielded on receipt. If you omit checklast, this is an infinite -- generator; it's up to the caller to recognize when the last reply has been @@ -178,7 +178,7 @@ function leap.generate(pump, data, checklast) -- Invent a new, unique reqid. Arrange to handle incoming events -- bearing that reqid. Stamp the outbound request with that reqid, and -- send it. - local reqid = leap._requestSetup(pump, data) + local reqid = requestSetup(pump, data) local waitfor = leap._pending[reqid] local ok, response repeat @@ -186,7 +186,6 @@ function leap.generate(pump, data, checklast) if not ok then break end - response.reqid = nil coroutine.yield(response) until checklast and checklast(response) -- If we break the above loop, whether or not due to error, clean up. @@ -196,78 +195,79 @@ function leap.generate(pump, data, checklast) end end --- Kick off response processing. The calling script must create and resume one --- or more coroutines to perform viewer requests using send(), request() or --- generate() before calling this function to handle responses. --- --- While waiting for responses from the viewer, the C++ coroutine running the --- calling Lua script is blocked: no other Lua coroutine is running. -function leap.process() - leap._done = false - local ok, pump, data - while not leap._done do --- print_debug('leap.process() calling get_event_next()') - ok, pump, data = pcall(get_event_next) --- print_debug('leap.process() got '..tostring(ok)..': '..pump..', '..inspect(data)) - -- ok false means get_event_next() raised a Lua error - -- data nil means get_event_next() returned (pump, LLSD()) to indicate done - if not (ok and data) then - break - end - leap._dispatch(pump, data) - end --- print_debug('leap.process() done') +local function cleanup(message) -- we're done: clean up all pending coroutines - -- if ok, then we're just done. - -- if not ok, then 'pump' is actually the error message. - message = if ok then 'done' else pump for i, waitfor in pairs(leap._pending) do - waitfor:_exception(message) + waitfor:exception(message) end for i, waitfor in pairs(leap._waitfors) do - waitfor:_exception(message) - end - -- now that we're done with cleanup, propagate the error we caught above - if not ok then - error(pump) + waitfor:exception(message) end end -function leap.done() - leap._done = true +-- Handle an incoming (pump, data) event with no recognizable ['reqid'] +local function unsolicited(pump, data) + -- we maintain waitfors in descending priority order, so the first waitfor + -- to claim this event is the one with the highest priority + for i, waitfor in pairs(leap._waitfors) do + debug('unsolicited() checking %s', waitfor.name) + if waitfor:handle(pump, data) then + return + end + end + print_debug(string.format('unsolicited(%s, %s) discarding unclaimed event', pump, data)) end -- Route incoming (pump, data) event to the appropriate waiting coroutine. -function leap._dispatch(pump, data) +local function dispatch(pump, data) local reqid = data['reqid'] -- if the response has no 'reqid', it's not from request() or generate() if reqid == nil then - return leap._unsolicited(pump, data) + return unsolicited(pump, data) end -- have reqid; do we have a WaitForReqid? local waitfor = leap._pending[reqid] if waitfor == nil then - return leap._unsolicited(pump, data) + return unsolicited(pump, data) end -- found the right WaitForReqid object, let it handle the event - data['reqid'] = nil - waitfor:_handle(pump, data) + waitfor:handle(pump, data) end --- Handle an incoming (pump, data) event with no recognizable ['reqid'] -function leap._unsolicited(pump, data) - -- we maintain waitfors in descending priority order, so the first waitfor - -- to claim this event is the one with the highest priority - for i, waitfor in pairs(leap._waitfors) do - if waitfor:_handle(pump, data) then - return - end +-- We configure fiber.set_idle() function. fiber.yield() calls the configured +-- idle callback whenever there are waiting fibers but no ready fibers. In +-- our case, that means it's time to fetch another incoming viewer event. +fiber.set_idle(function () + -- If someone has called leap.done(), then tell fiber.yield() to break loop. + if leap._done then + cleanup('done') + return 'done' + end + debug('leap.idle() calling get_event_next()') + local ok, pump, data = pcall(get_event_next) + debug('leap.idle() got %s: %s, %s', ok, pump, data) + -- ok false means get_event_next() raised a Lua error, pump is message + if not ok then + cleanup(pump) + error(pump) + end + -- data nil means get_event_next() returned (pump, LLSD()) to indicate done + if not data then + cleanup('end') + return 'end' end --- print_debug('_unsolicited(', pump, ', ', data, ') discarding unclaimed event') + -- got a real pump, data pair + dispatch(pump, data) + -- return to fiber.yield(): any incoming message might result in one or + -- more fibers becoming ready +end) + +function leap.done() + leap._done = true end -- called by WaitFor.enable() -function leap._registerWaitFor(waitfor) +local function registerWaitFor(waitfor) table.insert(leap._waitfors, waitfor) -- keep waitfors sorted in descending order of specified priority table.sort(leap._waitfors, @@ -275,7 +275,7 @@ function leap._registerWaitFor(waitfor) end -- called by WaitFor.disable() -function leap._unregisterWaitFor(waitfor) +local function unregisterWaitFor(waitfor) for i, w in pairs(leap._waitfors) do if w == waitfor then leap._waitfors[i] = nil @@ -322,8 +322,13 @@ end -- --------------------------------- WaitFor --------------------------------- leap.WaitFor = { _id=0 } +function leap.WaitFor.tostring(self) + -- Lua (sub)classes have no name; can't prefix with that + return self.name +end + function leap.WaitFor:new(priority, name) - local obj = setmetatable({}, self) + local obj = setmetatable({__tostring=leap.WaitFor.tostring}, self) self.__index = self obj.priority = priority @@ -343,16 +348,11 @@ function leap.WaitFor:new(priority, name) return obj end -function leap.WaitFor.tostring(self) - -- Lua (sub)classes have no name; can't prefix with that - return self.name -end - -- Re-enable a disable()d WaitFor object. New WaitFor objects are -- enable()d by default. function leap.WaitFor:enable() if not self._registered then - leap._registerWaitFor(self) + registerWaitFor(self) self._registered = true end end @@ -360,7 +360,7 @@ end -- Disable an enable()d WaitFor object. function leap.WaitFor:disable() if self._registered then - leap._unregisterWaitFor(self) + unregisterWaitFor(self) self._registered = false end end @@ -368,18 +368,12 @@ end -- Block the calling coroutine until a suitable unsolicited event (one -- for which filter() returns the event) arrives. function leap.WaitFor:wait() --- print_debug(self.name .. ' about to wait') - item = self._queue:Dequeue() --- print_debug(self.name .. ' got ', item) + debug('%s about to wait', self.name) + local item = self._queue:Dequeue() + debug('%s got %s', self.name, item) return item end --- Loop over wait() calls. -function leap.WaitFor:iterate() - -- on each iteration, call self.wait(self) - return self.wait, self, nil -end - -- Override filter() to examine the incoming event in whatever way -- makes sense. -- @@ -395,9 +389,10 @@ function leap.WaitFor:filter(pump, data) error('You must override the WaitFor.filter() method') end --- called by leap._unsolicited() for each WaitFor in leap._waitfors -function leap.WaitFor:_handle(pump, data) - item = self:filter(pump, data) +-- called by unsolicited() for each WaitFor in leap._waitfors +function leap.WaitFor:handle(pump, data) + local item = self:filter(pump, data) + debug('%s.filter() returned %s', self.name, item) -- if this item doesn't pass the filter, we're not interested if not item then return false @@ -407,13 +402,13 @@ function leap.WaitFor:_handle(pump, data) return true end --- called by WaitFor:_handle() for an accepted event +-- called by WaitFor:handle() for an accepted event function leap.WaitFor:process(item) self._queue:Enqueue(item) end -- called by leap.process() when get_event_next() raises an error -function leap.WaitFor:_exception(message) +function leap.WaitFor:exception(message) print_warning(self.name .. ' error: ' .. message) self._queue:Error(message) end diff --git a/indra/newview/scripts/lua/printf.lua b/indra/newview/scripts/lua/printf.lua new file mode 100644 index 0000000000..584cd4f391 --- /dev/null +++ b/indra/newview/scripts/lua/printf.lua @@ -0,0 +1,19 @@ +-- printf(...) is short for print(string.format(...)) + +local inspect = require 'inspect' + +local function printf(...) + -- string.format() only handles numbers and strings. + -- Convert anything else to string using the inspect module. + local args = {} + for _, arg in pairs(table.pack(...)) do + if type(arg) == 'number' or type(arg) == 'string' then + table.insert(args, arg) + else + table.insert(args, inspect(arg)) + end + end + print(string.format(table.unpack(args))) +end + +return printf diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index 069e10e9cf..1dd081fb98 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -311,23 +311,27 @@ namespace tut const std::string lua( "-- test leap.lua\n" "\n" + "fiber = require('fiber')\n" "leap = require('leap')\n" - "coro = require('coro')\n" + "-- debug = require('printf')\n" + "local function debug(...) end\n" "\n" "-- negative priority ensures catchall is always last\n" "catchall = leap.WaitFor:new(-1, 'catchall')\n" "function catchall:filter(pump, data)\n" + " debug('catchall:filter(%s, %s)', pump, data)\n" " return data\n" "end\n" "\n" "-- but first, catch events with 'special' key\n" "catch_special = leap.WaitFor:new(2, 'catch_special')\n" "function catch_special:filter(pump, data)\n" + " debug('catch_special:filter(%s, %s)', pump, data)\n" " return if data['special'] ~= nil then data else nil\n" "end\n" "\n" "function drain(waitfor)\n" - " print(waitfor.name .. ' start')\n" + " debug('%s start', waitfor.name)\n" " -- It seems as though we ought to be able to code this loop\n" " -- over waitfor:wait() as:\n" " -- for item in waitfor.wait, waitfor do\n" @@ -335,28 +339,30 @@ namespace tut " -- the coroutine call stack, which prohibits coroutine.yield():\n" " -- 'attempt to yield across metamethod/C-call boundary'\n" " -- So we resort to two different calls to waitfor:wait().\n" - " item = waitfor:wait()\n" + " local item = waitfor:wait()\n" " while item do\n" - " print(waitfor.name .. ' caught', item)\n" + " debug('%s caught %s', waitfor.name, item)\n" " item = waitfor:wait()\n" " end\n" - " print(waitfor.name .. ' done')\n" + " debug('%s done', waitfor.name)\n" "end\n" "\n" "function requester(name)\n" - " print('requester('..name..') start')\n" - " response = leap.request('testpump', {name=name})\n" - " print('requester('..name..') got '..tostring(response))\n" + " debug('requester(%s) start', name)\n" + " local response = leap.request('testpump', {name=name})\n" + " debug('requester(%s) got %s', name, response)\n" " -- verify that the correct response was dispatched to this coroutine\n" " assert(response.name == name)\n" "end\n" "\n" - "coro.launch(drain, catchall)\n" - "coro.launch(drain, catch_special)\n" - "coro.launch(requester, 'a')\n" - "coro.launch(requester, 'b')\n" - "\n" - "leap.process()\n" + "-- fiber.print_all()\n" + "fiber.launch('catchall', drain, catchall)\n" + "fiber.launch('catch_special', drain, catch_special)\n" + "fiber.launch('requester(a)', requester, 'a')\n" + "-- requester(a)\n" + "fiber.launch('requester(b)', requester, 'b')\n" + "-- fiber.print_all()\n" + "-- fiber.run()\n" ); LLSD requests; -- cgit v1.2.3 From 76752d6fc00a2789d96480da2a1e862ffecc812a Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Thu, 21 Mar 2024 18:26:48 +0200 Subject: Switch to LLDispatchListener --- indra/llui/llluafloater.cpp | 68 ++++++++-------------- indra/llui/llluafloater.h | 4 +- indra/newview/scripts/lua/test_luafloater_demo.lua | 19 +++--- .../scripts/lua/test_luafloater_gesture_list.lua | 14 +++-- 4 files changed, 44 insertions(+), 61 deletions(-) (limited to 'indra') diff --git a/indra/llui/llluafloater.cpp b/indra/llui/llluafloater.cpp index 668c0edc53..4ae0e28963 100644 --- a/indra/llui/llluafloater.cpp +++ b/indra/llui/llluafloater.cpp @@ -49,53 +49,40 @@ std::map EVENT_LIST = {"CLOSE_EVENT", "floater_close"} }; + + LLLuaFloater::LLLuaFloater(const LLSD &key) : - LLFloater(key), + LLFloater(key), + mDispatchListener(LLUUID::generateNewID().asString(), "action"), mReplyPumpName(key["reply"].asString()), - mDispatcher("LLLuaFloater", "action"), mReqID(key) { - mListenerPumpName = LLUUID::generateNewID().asString(); - - mBoundListener = LLEventPumps::instance().obtain(mListenerPumpName).listen(LISTENER_NAME, [this](const LLSD &event) - { - if (event.has("action")) - { - mDispatcher.try_call(event); - } - else + auto ctrl_lookup = [this](const LLSD &event, std::function cb) + { + LLUICtrl *ctrl = getChild(event["ctrl_name"].asString()); + if (!ctrl) { - LL_WARNS("LuaFloater") << "Unknown message: " << event << LL_ENDL; + LL_WARNS("LuaFloater") << "Control not found: " << event["ctrl_name"] << LL_ENDL; + return LLSD(); } - return false; - }); + return cb(ctrl, event); + }; LLSD requiredParams = llsd::map("ctrl_name", LLSD(), "value", LLSD()); - mDispatcher.add("set_enabled", "", [this](const LLSD &event) + mDispatchListener.add("set_enabled", "", [this, ctrl_lookup](const LLSD &event) { - LLUICtrl *ctrl = getChild(event["ctrl_name"].asString()); - if(ctrl) - { - ctrl->setEnabled(event["value"].asBoolean()); - } + return ctrl_lookup(event, [](LLUICtrl *ctrl, const LLSD &event) { ctrl->setEnabled(event["value"].asBoolean()); return LLSD(); }); }, requiredParams); - mDispatcher.add("set_visible", "", [this](const LLSD &event) + mDispatchListener.add("set_visible", "", [this, ctrl_lookup](const LLSD &event) { - LLUICtrl *ctrl = getChild(event["ctrl_name"].asString()); - if(ctrl) - { - ctrl->setVisible(event["value"].asBoolean()); - } + return ctrl_lookup(event, [](LLUICtrl *ctrl, const LLSD &event) { ctrl->setVisible(event["value"].asBoolean()); return LLSD(); }); }, requiredParams); - mDispatcher.add("set_value", "", [this](const LLSD &event) + mDispatchListener.add("set_value", "", [this, ctrl_lookup](const LLSD &event) { - LLUICtrl *ctrl = getChild(event["ctrl_name"].asString()); - if(ctrl) - { - ctrl->setValue(event["value"]); - } + return ctrl_lookup(event, [](LLUICtrl *ctrl, const LLSD &event) { ctrl->setValue(event["value"]); return LLSD(); }); }, requiredParams); - mDispatcher.add("add_list_element", "", [this](const LLSD &event) + + mDispatchListener.add("add_list_element", "", [this](const LLSD &event) { LLScrollListCtrl *ctrl = getChild(event["ctrl_name"].asString()); if(ctrl) @@ -104,21 +91,14 @@ LLLuaFloater::LLLuaFloater(const LLSD &key) : } }, requiredParams); - mDispatcher.add("set_title", "", [this](const LLSD &event) + mDispatchListener.add("set_title", "", [this](const LLSD &event) { setTitle(event["value"].asString()); }, llsd::map("value", LLSD())); - mDispatcher.add("get_value", "", [this](const LLSD &event) + mDispatchListener.add("get_value", "", [this, ctrl_lookup](const LLSD &event) { - LLUICtrl *ctrl = getChild(event["ctrl_name"].asString()); - if(ctrl) - { - LLSD response; - response["value"] = ctrl->getValue(); - response["reqid"] = event["reqid"]; - post(response); - } + return ctrl_lookup(event, [](LLUICtrl *ctrl, const LLSD &event) { return llsd::map("value", ctrl->getValue()); }); }, llsd::map("ctrl_name", LLSD(), "reqid", LLSD())); } @@ -162,7 +142,7 @@ BOOL LLLuaFloater::postBuild() } //send pump name to the script after the floater is built - post(llsd::map("command_name", mListenerPumpName, "event", EVENT_LIST["POST_BUILD_EVENT"])); + post(llsd::map("command_name", mDispatchListener.getPumpName(), "event", EVENT_LIST["POST_BUILD_EVENT"])); return true; } diff --git a/indra/llui/llluafloater.h b/indra/llui/llluafloater.h index b9f96f0ad3..d4c16745ee 100644 --- a/indra/llui/llluafloater.h +++ b/indra/llui/llluafloater.h @@ -46,10 +46,8 @@ public: private: LLReqID mReqID; - LLEventDispatcher mDispatcher; - LLTempBoundListener mBoundListener; + LLDispatchListener mDispatchListener; - std::string mListenerPumpName; std::string mReplyPumpName; }; #endif diff --git a/indra/newview/scripts/lua/test_luafloater_demo.lua b/indra/newview/scripts/lua/test_luafloater_demo.lua index 2cbafcec14..308cebcb88 100644 --- a/indra/newview/scripts/lua/test_luafloater_demo.lua +++ b/indra/newview/scripts/lua/test_luafloater_demo.lua @@ -39,12 +39,19 @@ function handleEvents(event_data) elseif event_data.event == e.CLOSE_EVENT then print_warning("Floater was closed") leap.done() - --script received event pump name, after floater was built - elseif event_data.event == e.POST_BUILD_EVENT then - COMMAND_PUMP_NAME = event_data.command_name end end +local key = {xml_path = XML_FILE_PATH, op = "showLuaFloater"} +--sign for additional events for defined control {= {action1, action2, ...}} +key.extra_events={show_time_lbl = {e.RIGHT_MOUSE_DOWN_EVENT, e.DOUBLE_CLICK_EVENT}} +coro.launch(function () + --script received event pump name, after floater was built + COMMAND_PUMP_NAME = leap.request("LLFloaterReg", key)["command_name"] + leap.done() +end) +leap.process() + catch_events = leap.WaitFor:new(-1, "all_events") function catch_events:filter(pump, data) return data @@ -58,12 +65,6 @@ function process_events(waitfor) end end -local key = {xml_path = XML_FILE_PATH, op = "showLuaFloater"} - ---sign for additional events for defined control {= {action1, action2, ...}} -key.extra_events={show_time_lbl = {e.RIGHT_MOUSE_DOWN_EVENT, e.DOUBLE_CLICK_EVENT}} -leap.send("LLFloaterReg", key, "floater1") - coro.launch(process_events, catch_events) leap.process() print_warning("End of the script") diff --git a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua index 5ea2b1e30d..57f737ce9b 100644 --- a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua +++ b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua @@ -40,6 +40,15 @@ function handleEvents(event_data) end end +local key = {xml_path = XML_FILE_PATH, op = "showLuaFloater"} +--receive additional events for defined control {= {action1, action2, ...}} +key.extra_events={gesture_list = {e.DOUBLE_CLICK_EVENT}} +coro.launch(function () + handleEvents(leap.request("LLFloaterReg", key)) + leap.done() +end) +leap.process() + catch_events = leap.WaitFor:new(-1, "all_events") function catch_events:filter(pump, data) return data @@ -53,10 +62,5 @@ function process_events(waitfor) end end -local key = {xml_path = XML_FILE_PATH, op = "showLuaFloater"} ---receive additional events for defined control {= {action1, action2, ...}} -key.extra_events={gesture_list = {e.DOUBLE_CLICK_EVENT}} -leap.send("LLFloaterReg", key, "floater1") - coro.launch(process_events, catch_events) leap.process() -- cgit v1.2.3 From 4ffdae72392ba2f081edf8d740b688b95ac4fc65 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Thu, 21 Mar 2024 20:24:24 +0200 Subject: Accept an array for "add_list_item" and change EVENT_LIST type --- indra/llui/llluafloater.cpp | 73 ++++++++++++++-------- indra/llui/llluafloater.h | 1 + indra/newview/scripts/lua/test_luafloater_demo.lua | 20 ++++-- .../scripts/lua/test_luafloater_gesture_list.lua | 26 +++++--- indra/newview/scripts/lua/util.lua | 10 +++ 5 files changed, 88 insertions(+), 42 deletions(-) (limited to 'indra') diff --git a/indra/llui/llluafloater.cpp b/indra/llui/llluafloater.cpp index 4ae0e28963..23a336a1e8 100644 --- a/indra/llui/llluafloater.cpp +++ b/indra/llui/llluafloater.cpp @@ -35,22 +35,19 @@ const std::string LISTENER_NAME("LLLuaFloater"); -std::map EVENT_LIST = -{ - {"COMMIT_EVENT", "commit"}, - {"DOUBLE_CLICK_EVENT", "double_click"}, - {"MOUSE_ENTER_EVENT", "mouse_enter"}, - {"MOUSE_LEAVE_EVENT", "mouse_leave"}, - {"MOUSE_DOWN_EVENT", "mouse_down"}, - {"MOUSE_UP_EVENT", "mouse_up"}, - {"RIGHT_MOUSE_DOWN_EVENT", "right_mouse_down"}, - {"RIGHT_MOUSE_UP_EVENT", "right_mouse_up"}, - {"POST_BUILD_EVENT", "post_build"}, - {"CLOSE_EVENT", "floater_close"} +std::set EVENT_LIST = { + "commit", + "double_click", + "mouse_enter", + "mouse_leave", + "mouse_down", + "mouse_up", + "right_mouse_down", + "right_mouse_up", + "post_build", + "floater_close" }; - - LLLuaFloater::LLLuaFloater(const LLSD &key) : LLFloater(key), mDispatchListener(LLUUID::generateNewID().asString(), "action"), @@ -87,7 +84,18 @@ LLLuaFloater::LLLuaFloater(const LLSD &key) : LLScrollListCtrl *ctrl = getChild(event["ctrl_name"].asString()); if(ctrl) { - ctrl->addElement(event["value"]); + LLSD element_data = event["value"]; + if (element_data.isArray()) + { + for (const auto &row : llsd::inArray(element_data)) + { + ctrl->addElement(row); + } + } + else + { + ctrl->addElement(element_data); + } } }, requiredParams); @@ -117,13 +125,12 @@ BOOL LLLuaFloater::postBuild() { LLSD data; data["ctrl_name"] = view->getName(); - data["event"] = EVENT_LIST["COMMIT_EVENT"]; ctrl->setCommitCallback([this, data](LLUICtrl *ctrl, const LLSD ¶m) { LLSD event(data); event["value"] = ctrl->getValue(); - post(event); + postEvent(event, "commit"); }); } } @@ -142,14 +149,20 @@ BOOL LLLuaFloater::postBuild() } //send pump name to the script after the floater is built - post(llsd::map("command_name", mDispatchListener.getPumpName(), "event", EVENT_LIST["POST_BUILD_EVENT"])); + postEvent(llsd::map("command_name", mDispatchListener.getPumpName()), "post_build"); return true; } void LLLuaFloater::onClose(bool app_quitting) { - post(llsd::map("event", EVENT_LIST["CLOSE_EVENT"], "app_quitting", app_quitting)); + postEvent(llsd::map("app_quitting", app_quitting), "floater_close"); +} + +bool event_is(const std::string &event_name, const std::string &list_event) +{ + llassert(EVENT_LIST.find(list_event) != EVENT_LIST.end()); + return (event_name == list_event); } void LLLuaFloater::registerCallback(const std::string &ctrl_name, const std::string &event) @@ -169,31 +182,31 @@ void LLLuaFloater::registerCallback(const std::string &ctrl_name, const std::str post(event.with("x", x).with("y", y)); }; - if (event == EVENT_LIST["MOUSE_ENTER_EVENT"]) + if (event_is(event, "mouse_enter")) { ctrl->setMouseEnterCallback(mouse_event_cb); } - else if (event == EVENT_LIST["MOUSE_LEAVE_EVENT"]) + else if (event_is(event, "mouse_leave")) { ctrl->setMouseLeaveCallback(mouse_event_cb); } - else if (event == EVENT_LIST["MOUSE_DOWN_EVENT"]) + else if (event_is(event, "mouse_down")) { ctrl->setMouseDownCallback(mouse_event_coords_cb); } - else if (event == EVENT_LIST["MOUSE_UP_EVENT"]) + else if (event_is(event, "mouse_up")) { ctrl->setMouseUpCallback(mouse_event_coords_cb); } - else if (event == EVENT_LIST["RIGHT_MOUSE_DOWN_EVENT"]) + else if (event_is(event, "right_mouse_down")) { ctrl->setRightMouseDownCallback(mouse_event_coords_cb); } - else if (event == EVENT_LIST["RIGHT_MOUSE_UP_EVENT"]) + else if (event_is(event, "right_mouse_up")) { ctrl->setRightMouseUpCallback(mouse_event_coords_cb); } - else if (event == EVENT_LIST["DOUBLE_CLICK_EVENT"]) + else if (event_is(event, "double_click")) { LLScrollListCtrl *list = dynamic_cast(ctrl); if (list) @@ -224,6 +237,12 @@ void LLLuaFloater::post(const LLSD &data) LLEventPumps::instance().obtain(mReplyPumpName).post(stamped_data); } +void LLLuaFloater::postEvent(LLSD data, const std::string &event_name) +{ + llassert(EVENT_LIST.find(event_name) != EVENT_LIST.end()); + post(data.with("event", event_name)); +} + void LLLuaFloater::showLuaFloater(const LLSD &data) { std::filesystem::path fs_path(data["xml_path"].asString()); @@ -244,7 +263,7 @@ LLSD LLLuaFloater::getEventsData() LLSD event_data; for (auto &it : EVENT_LIST) { - event_data[it.first] = it.second; + event_data.append(it); } return event_data; } diff --git a/indra/llui/llluafloater.h b/indra/llui/llluafloater.h index d4c16745ee..ccc3ccb39b 100644 --- a/indra/llui/llluafloater.h +++ b/indra/llui/llluafloater.h @@ -41,6 +41,7 @@ public: void onClose(bool app_quitting); void post(const LLSD &data); + void postEvent(LLSD data, const std::string &event); static void showLuaFloater(const LLSD &data); static LLSD getEventsData(); diff --git a/indra/newview/scripts/lua/test_luafloater_demo.lua b/indra/newview/scripts/lua/test_luafloater_demo.lua index 308cebcb88..b81259c060 100644 --- a/indra/newview/scripts/lua/test_luafloater_demo.lua +++ b/indra/newview/scripts/lua/test_luafloater_demo.lua @@ -2,17 +2,25 @@ XML_FILE_PATH = "luafloater_demo.xml" leap = require 'leap' coro = require 'coro' +util = require 'util' --event pump for sending actions to the floater COMMAND_PUMP_NAME = "" --table of floater UI events -e={} +event_list={} coro.launch(function () - e = leap.request("LLFloaterReg", {op="getFloaterEvents"})["events"] + event_list = leap.request("LLFloaterReg", {op="getFloaterEvents"})["events"] leap.done() end) leap.process() +local function _event(event_name) + if not util.contains(event_list, event_name) then + print_warning("Incorrect event name: " .. event_name) + end + return event_name +end + function post(action) leap.send(COMMAND_PUMP_NAME, action) end @@ -23,7 +31,7 @@ function getCurrentTime() end function handleEvents(event_data) - if event_data.event == e.COMMIT_EVENT then + if event_data.event == _event("commit") then if event_data.ctrl_name == "disable_ctrl" then post({action="set_enabled", ctrl_name="open_btn", value = (1 - event_data.value)}) elseif event_data.ctrl_name == "title_cmb" then @@ -32,11 +40,11 @@ function handleEvents(event_data) floater_name = leap.request(COMMAND_PUMP_NAME, {action="get_value", ctrl_name='openfloater_cmd'})['value'] leap.send("LLFloaterReg", {name = floater_name, op = "showInstance"}) end - elseif event_data.event == e.DOUBLE_CLICK_EVENT then + elseif event_data.event == _event("double_click") then if event_data.ctrl_name == "show_time_lbl" then post({action="set_value", ctrl_name="time_lbl", value= getCurrentTime()}) end - elseif event_data.event == e.CLOSE_EVENT then + elseif event_data.event == _event("floater_close") then print_warning("Floater was closed") leap.done() end @@ -44,7 +52,7 @@ end local key = {xml_path = XML_FILE_PATH, op = "showLuaFloater"} --sign for additional events for defined control {= {action1, action2, ...}} -key.extra_events={show_time_lbl = {e.RIGHT_MOUSE_DOWN_EVENT, e.DOUBLE_CLICK_EVENT}} +key.extra_events={show_time_lbl = {_event("right_mouse_down"), _event("double_click")}} coro.launch(function () --script received event pump name, after floater was built COMMAND_PUMP_NAME = leap.request("LLFloaterReg", key)["command_name"] diff --git a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua index 57f737ce9b..b46e36b4d9 100644 --- a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua +++ b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua @@ -2,26 +2,34 @@ XML_FILE_PATH = "luafloater_gesture_list.xml" leap = require 'leap' coro = require 'coro' +util = require 'util' LLGesture = require 'LLGesture' --event pump for sending actions to the floater COMMAND_PUMP_NAME = "" --table of floater UI events -e={} +event_list={} coro.launch(function () - e = leap.request("LLFloaterReg", {op="getFloaterEvents"})["events"] + event_list = leap.request("LLFloaterReg", {op="getFloaterEvents"})["events"] leap.done() end) leap.process() +local function _event(event_name) + if not util.contains(event_list, event_name) then + print_warning("Incorrect event name: " .. event_name) + end + return event_name +end + function post(action) leap.send(COMMAND_PUMP_NAME, action) end function handleEvents(event_data) - if event_data.event == e.CLOSE_EVENT then + if event_data.event == _event("floater_close") then leap.done() - elseif event_data.event == e.POST_BUILD_EVENT then + elseif event_data.event == _event("post_build") then COMMAND_PUMP_NAME = event_data.command_name gestures_uuid = LLGesture.getActiveGestures() local action_data = {} @@ -29,11 +37,11 @@ function handleEvents(event_data) action_data.ctrl_name = "gesture_list" gestures = {} for uuid, info in pairs(gestures_uuid) do - element={value = uuid, columns ={column = "gesture_name", value = info.name}} - action_data.value = element - post(action_data) + table.insert(gestures, {value = uuid, columns ={column = "gesture_name", value = info.name}}) end - elseif event_data.event == e.DOUBLE_CLICK_EVENT then + action_data.value = gestures + post(action_data) + elseif event_data.event == _event("double_click") then if event_data.ctrl_name == "gesture_list" then LLGesture.startGesture(event_data.value) end @@ -42,7 +50,7 @@ end local key = {xml_path = XML_FILE_PATH, op = "showLuaFloater"} --receive additional events for defined control {= {action1, action2, ...}} -key.extra_events={gesture_list = {e.DOUBLE_CLICK_EVENT}} +key.extra_events={gesture_list = {_event("double_click")}} coro.launch(function () handleEvents(leap.request("LLFloaterReg", key)) leap.done() diff --git a/indra/newview/scripts/lua/util.lua b/indra/newview/scripts/lua/util.lua index e3af633ea7..5d6042dfe5 100644 --- a/indra/newview/scripts/lua/util.lua +++ b/indra/newview/scripts/lua/util.lua @@ -36,4 +36,14 @@ function util.equal(t1, t2) return util.empty(temp) end +-- check if array-like table contains certain value +function util.contains(t, v) + for _, value in ipairs(t) do + if value == v then + return true + end + end + return false +end + return util -- cgit v1.2.3 From 7b149485e091880269dd9d7941cca47da49e652f Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Thu, 21 Mar 2024 22:05:37 +0200 Subject: mac build fix --- indra/llui/llluafloater.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/llui/llluafloater.cpp b/indra/llui/llluafloater.cpp index 23a336a1e8..afc287a864 100644 --- a/indra/llui/llluafloater.cpp +++ b/indra/llui/llluafloater.cpp @@ -66,15 +66,15 @@ LLLuaFloater::LLLuaFloater(const LLSD &key) : }; LLSD requiredParams = llsd::map("ctrl_name", LLSD(), "value", LLSD()); - mDispatchListener.add("set_enabled", "", [this, ctrl_lookup](const LLSD &event) + mDispatchListener.add("set_enabled", "", [ctrl_lookup](const LLSD &event) { return ctrl_lookup(event, [](LLUICtrl *ctrl, const LLSD &event) { ctrl->setEnabled(event["value"].asBoolean()); return LLSD(); }); }, requiredParams); - mDispatchListener.add("set_visible", "", [this, ctrl_lookup](const LLSD &event) + mDispatchListener.add("set_visible", "", [ctrl_lookup](const LLSD &event) { return ctrl_lookup(event, [](LLUICtrl *ctrl, const LLSD &event) { ctrl->setVisible(event["value"].asBoolean()); return LLSD(); }); }, requiredParams); - mDispatchListener.add("set_value", "", [this, ctrl_lookup](const LLSD &event) + mDispatchListener.add("set_value", "", [ctrl_lookup](const LLSD &event) { return ctrl_lookup(event, [](LLUICtrl *ctrl, const LLSD &event) { ctrl->setValue(event["value"]); return LLSD(); }); }, requiredParams); @@ -104,7 +104,7 @@ LLLuaFloater::LLLuaFloater(const LLSD &key) : setTitle(event["value"].asString()); }, llsd::map("value", LLSD())); - mDispatchListener.add("get_value", "", [this, ctrl_lookup](const LLSD &event) + mDispatchListener.add("get_value", "", [ctrl_lookup](const LLSD &event) { return ctrl_lookup(event, [](LLUICtrl *ctrl, const LLSD &event) { return llsd::map("value", ctrl->getValue()); }); }, llsd::map("ctrl_name", LLSD(), "reqid", LLSD())); -- cgit v1.2.3 From bb39a8b223f78205a10ffcb61e3b3bfe05b3fd1a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 22 Mar 2024 21:04:48 +0900 Subject: Fix a couple bugs in fiber.lua machinery. This fixes a hang if the Lua script explicitly calls fiber.run() before LuaState::expr()'s implicit fiber.run() call. Make fiber.run() remove the calling fiber from the ready list to avoid an infinite loop when all other fibers have terminated: "You're ready!" "Okay, yield()." "You're ready again!" ... But don't claim it's waiting, either, because then when all other fibers have terminated, we'd call idle() in the vain hope that something would make that one last fiber ready. WaitQueue:_wake_waiters() needs to wake waiting fibers if the queue's not empty OR it's been closed. Introduce leap.WaitFor:close() to close the queue gracefully so that a looping waiter can terminate, instead of using WaitFor:exception(), which stops the whole script once it propagates. Make leap's cleanup() function call close(). Streamline fiber.get_name() by using 'or' instead of if ... then. Streamline fiber.status() and fiber.set_waiting() by using table.find() instead of a loop. --- indra/newview/scripts/lua/ErrorQueue.lua | 4 +++ indra/newview/scripts/lua/WaitQueue.lua | 2 +- indra/newview/scripts/lua/fiber.lua | 42 ++++++++++++++----------------- indra/newview/scripts/lua/leap.lua | 9 +++++-- indra/newview/tests/llluamanager_test.cpp | 2 +- 5 files changed, 32 insertions(+), 27 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/ErrorQueue.lua b/indra/newview/scripts/lua/ErrorQueue.lua index a6d4470044..076742815a 100644 --- a/indra/newview/scripts/lua/ErrorQueue.lua +++ b/indra/newview/scripts/lua/ErrorQueue.lua @@ -3,18 +3,22 @@ -- raise that error. local WaitQueue = require('WaitQueue') +-- local debug = require('printf') +local function debug(...) end local ErrorQueue = WaitQueue:new() function ErrorQueue:Error(message) -- Setting Error() is a marker, like closing the queue. Once we reach the -- error, every subsequent Dequeue() call will raise the same error. + debug('Setting self._closed to %q', message) self._closed = message self:_wake_waiters() end function ErrorQueue:Dequeue() local value = WaitQueue.Dequeue(self) + debug('ErrorQueue:Dequeue: base Dequeue() got %s', value) if value ~= nil then -- queue not yet closed, show caller return value diff --git a/indra/newview/scripts/lua/WaitQueue.lua b/indra/newview/scripts/lua/WaitQueue.lua index b15e9c443b..f69baff09b 100644 --- a/indra/newview/scripts/lua/WaitQueue.lua +++ b/indra/newview/scripts/lua/WaitQueue.lua @@ -38,7 +38,7 @@ function WaitQueue:_wake_waiters() -- Unlike OS threads, with cooperative concurrency it doesn't make sense -- to "notify all": we need wake only one of the waiting Dequeue() -- callers. - if not self:IsEmpty() and next(self._waiters) then + if ((not self:IsEmpty()) or self._closed) and next(self._waiters) then -- Pop the oldest waiting coroutine instead of the most recent, for -- more-or-less round robin fairness. But skip any coroutines that -- have gone dead in the meantime. diff --git a/indra/newview/scripts/lua/fiber.lua b/indra/newview/scripts/lua/fiber.lua index f18d133cc8..8ed99f12b7 100644 --- a/indra/newview/scripts/lua/fiber.lua +++ b/indra/newview/scripts/lua/fiber.lua @@ -104,13 +104,7 @@ end -- Query a fiber's name (nil for the running fiber) function fiber.get_name(co) - if not co then - co = fiber.running() - end - if not names[co] then - return 'unknown' - end - return names[co] + return names[co or fiber.running()] or 'unknown' end -- Query status of the passed fiber @@ -140,10 +134,8 @@ function fiber.status(co) return 'waiting' end -- not waiting should imply ready: sanity check - for _, maybe in pairs(ready) do - if maybe == co then - return 'ready' - end + if table.find(ready, co) then + return 'ready' end -- Calls within yield() between popping the next ready fiber and -- re-appending it to the list are in this state. Once we're done @@ -158,11 +150,9 @@ local function set_waiting() -- if called from the main fiber, inject a 'main' marker into the list co = fiber.running() -- delete from ready list - for i, maybe in pairs(ready) do - if maybe == co then - table.remove(ready, i) - break - end + local i = table.find(ready, co) + if i then + table.remove(ready, i) end -- add to waiting list waiting[co] = true @@ -191,11 +181,16 @@ end -- Run fibers until all but main have terminated: return nil. -- Or until configured idle() callback returns x ~= nil: return x. function fiber.run() - -- A fiber calling run() is not also doing other useful work. Tell yield() - -- that we're waiting. Otherwise it would keep seeing that our caller is - -- ready and return to us, instead of realizing that all coroutines are - -- waiting and call idle(). - set_waiting() + -- A fiber calling run() is not also doing other useful work. Remove the + -- calling fiber from the ready list. Otherwise yield() would keep seeing + -- that our caller is ready and return to us, instead of realizing that + -- all coroutines are waiting and call idle(). But don't say we're + -- waiting, either, because then when all other fibers have terminated + -- we'd call idle() forever waiting for something to make us ready again. + local i = table.find(ready, fiber.running()) + if i then + table.remove(ready, i) + end local others, idle_done repeat debug('%s calling fiber.run() calling yield()', fiber.get_name()) @@ -204,9 +199,10 @@ function fiber.run() tostring(others), tostring(idle_done)) until (not others) debug('%s fiber.run() done', fiber.get_name()) - fiber.wake(fiber.running()) + -- For whatever it's worth, put our own fiber back in the ready list. + table.insert(ready, fiber.running()) -- Once there are no more waiting fibers, and the only ready fiber is - -- main, return to main. All previously-launched fibers are done. Possibly + -- us, return to caller. All previously-launched fibers are done. Possibly -- the chunk is done, or the chunk may decide to launch a new batch of -- fibers. return idle_done diff --git a/indra/newview/scripts/lua/leap.lua b/indra/newview/scripts/lua/leap.lua index 60e8266a76..77f3a3e116 100644 --- a/indra/newview/scripts/lua/leap.lua +++ b/indra/newview/scripts/lua/leap.lua @@ -198,10 +198,10 @@ end local function cleanup(message) -- we're done: clean up all pending coroutines for i, waitfor in pairs(leap._pending) do - waitfor:exception(message) + waitfor:close() end for i, waitfor in pairs(leap._waitfors) do - waitfor:exception(message) + waitfor:close() end end @@ -407,6 +407,11 @@ function leap.WaitFor:process(item) self._queue:Enqueue(item) end +-- called by cleanup() at end +function leap.WaitFor:close() + self._queue:close() +end + -- called by leap.process() when get_event_next() raises an error function leap.WaitFor:exception(message) print_warning(self.name .. ' error: ' .. message) diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index 1dd081fb98..d1beed84ef 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -361,8 +361,8 @@ namespace tut "fiber.launch('requester(a)', requester, 'a')\n" "-- requester(a)\n" "fiber.launch('requester(b)', requester, 'b')\n" + "fiber.run()\n" "-- fiber.print_all()\n" - "-- fiber.run()\n" ); LLSD requests; -- cgit v1.2.3 From de1fc577666686fb0c3f8b38d8c6c90eb6dff414 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 23 Mar 2024 17:14:33 +0900 Subject: Update sendReply(): accepting LLSD by value already copies it. --- indra/llcommon/llevents.cpp | 13 +++++-------- indra/llcommon/llevents.h | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) (limited to 'indra') 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..1f35a6de18 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -779,7 +779,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) */ -- cgit v1.2.3 From 2dc003779443db99f46b3db6d17a1954f7b141dd Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 23 Mar 2024 17:43:07 +0900 Subject: Make leap.request() work even from Lua's main thread. Recast fiber.yield() as internal function scheduler(). Move fiber.run() after it so it can call scheduler() as a local function. Add new fiber.yield() that also calls scheduler(); the added value of this new fiber.yield() over plain scheduler() is that if scheduler() returns before the caller is ready (because the configured set_idle() function returned non-nil), it produces an explicit error rather than returning to its caller. So the caller can assume that when fiber.yield() returns normally, the calling fiber is ready. This allows any fiber, including the main thread, to call fiber.yield() or fiber.wait(). This supports using leap.request(), which posts a request and then waits on a WaitForReqid, which calls ErrorQueue:Dequeue(), which calls fiber.wait(). WaitQueue:_wake_waiters() must call fiber.status() instead of coroutine.status() so it understands the special token 'main'. Add a new llluamanager_test.cpp test to exercise calling leap.request() from Lua's main thread. --- indra/newview/scripts/lua/WaitQueue.lua | 2 +- indra/newview/scripts/lua/fiber.lua | 106 ++++++++++++++++++++---------- indra/newview/tests/llluamanager_test.cpp | 43 ++++++++++-- 3 files changed, 108 insertions(+), 43 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/WaitQueue.lua b/indra/newview/scripts/lua/WaitQueue.lua index f69baff09b..a34dbef4d7 100644 --- a/indra/newview/scripts/lua/WaitQueue.lua +++ b/indra/newview/scripts/lua/WaitQueue.lua @@ -43,7 +43,7 @@ function WaitQueue:_wake_waiters() -- more-or-less round robin fairness. But skip any coroutines that -- have gone dead in the meantime. local waiter = table.remove(self._waiters, 1) - while waiter and coroutine.status(waiter) ~= "suspended" do + while waiter and fiber.status(waiter) == "dead" do waiter = table.remove(self._waiters, 1) end -- do we still have at least one waiting coroutine? diff --git a/indra/newview/scripts/lua/fiber.lua b/indra/newview/scripts/lua/fiber.lua index 8ed99f12b7..7dc67f510c 100644 --- a/indra/newview/scripts/lua/fiber.lua +++ b/indra/newview/scripts/lua/fiber.lua @@ -178,36 +178,6 @@ function fiber.wake(co) -- but don't yet resume it: that happens next time we reach yield() end --- Run fibers until all but main have terminated: return nil. --- Or until configured idle() callback returns x ~= nil: return x. -function fiber.run() - -- A fiber calling run() is not also doing other useful work. Remove the - -- calling fiber from the ready list. Otherwise yield() would keep seeing - -- that our caller is ready and return to us, instead of realizing that - -- all coroutines are waiting and call idle(). But don't say we're - -- waiting, either, because then when all other fibers have terminated - -- we'd call idle() forever waiting for something to make us ready again. - local i = table.find(ready, fiber.running()) - if i then - table.remove(ready, i) - end - local others, idle_done - repeat - debug('%s calling fiber.run() calling yield()', fiber.get_name()) - others, idle_done = fiber.yield() - debug("%s fiber.run()'s yield() returned %s, %s", fiber.get_name(), - tostring(others), tostring(idle_done)) - until (not others) - debug('%s fiber.run() done', fiber.get_name()) - -- For whatever it's worth, put our own fiber back in the ready list. - table.insert(ready, fiber.running()) - -- Once there are no more waiting fibers, and the only ready fiber is - -- us, return to caller. All previously-launched fibers are done. Possibly - -- the chunk is done, or the chunk may decide to launch a new batch of - -- fibers. - return idle_done -end - -- pop and return the next not-dead fiber in the ready list, or nil if none remain local function live_ready_iter() -- don't write @@ -237,16 +207,24 @@ local function prune_waiting() end end --- Give other ready fibers a chance to run, leaving this one ready, returning --- after a cycle. Returns: --- * true, nil if there remain other live fibers, whether ready or waiting +-- Run other ready fibers, leaving this one ready, returning after a cycle. +-- Returns: +-- * true, nil if there remain other live fibers, whether ready or waiting, +-- but it's our turn to run -- * false, nil if this is the only remaining fiber --- * nil, x if configured idle() callback returned non-nil x -function fiber.yield() +-- * nil, x if configured idle() callback returns non-nil x +local function scheduler() + -- scheduler() is asymmetric because Lua distinguishes the main thread + -- from other coroutines. The main thread can't yield; it can only resume + -- other coroutines. So although an arbitrary coroutine could resume still + -- other arbitrary coroutines, it could NOT resume the main thread because + -- the main thread can't yield. Therefore, scheduler() delegates its real + -- processing to the main thread. If called from a coroutine, pass control + -- back to the main thread. if coroutine.running() then -- seize the opportunity to make sure the viewer isn't shutting down -- check_stop() - -- this is a real coroutine, yield normally to main or whoever + -- this is a real coroutine, yield normally to main thread coroutine.yield() -- main certainly still exists return true @@ -294,4 +272,60 @@ function fiber.yield() until false end +-- Let other fibers run. This is useful in either of two cases: +-- * fiber.wait() calls this to run other fibers while this one is waiting. +-- fiber.yield() (and therefore fiber.wait()) works from the main thread as +-- well as from explicitly-launched fibers, without the caller having to +-- care. +-- * A long-running fiber that doesn't often call fiber.wait() should sprinkle +-- in fiber.yield() calls to interleave processing on other fibers. +function fiber.yield() + -- The difference between this and fiber.run() is that fiber.yield() + -- assumes its caller has work to do. yield() returns to its caller as + -- soon as scheduler() pops this fiber from the ready list. fiber.run() + -- continues looping until all other fibers have terminated, or the + -- set_idle() callback tells it to stop. + local others, idle_done = scheduler() + -- scheduler() returns either if we're ready, or if idle_done ~= nil. + if idle_done ~= nil then + -- Returning normally from yield() means the caller can carry on with + -- its pending work. But in this case scheduler() returned because the + -- configured set_idle() function interrupted it -- not because we're + -- actually ready. Don't return normally. + error('fiber.set_idle() interrupted yield() with: ' .. tostring(idle_done)) + end + -- We're ready! Just return to caller. In this situation we don't care + -- whether there are other ready fibers. +end + +-- Run fibers until all but main have terminated: return nil. +-- Or until configured idle() callback returns x ~= nil: return x. +function fiber.run() + -- A fiber calling run() is not also doing other useful work. Remove the + -- calling fiber from the ready list. Otherwise yield() would keep seeing + -- that our caller is ready and return to us, instead of realizing that + -- all coroutines are waiting and call idle(). But don't say we're + -- waiting, either, because then when all other fibers have terminated + -- we'd call idle() forever waiting for something to make us ready again. + local i = table.find(ready, fiber.running()) + if i then + table.remove(ready, i) + end + local others, idle_done + repeat + debug('%s calling fiber.run() calling scheduler()', fiber.get_name()) + others, idle_done = scheduler() + debug("%s fiber.run()'s scheduler() returned %s, %s", fiber.get_name(), + tostring(others), tostring(idle_done)) + until (not others) + debug('%s fiber.run() done', fiber.get_name()) + -- For whatever it's worth, put our own fiber back in the ready list. + table.insert(ready, fiber.running()) + -- Once there are no more waiting fibers, and the only ready fiber is + -- us, return to caller. All previously-launched fibers are done. Possibly + -- the chunk is done, or the chunk may decide to launch a new batch of + -- fibers. + return idle_done +end + return fiber diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index d1beed84ef..0fd91c1354 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -307,9 +307,43 @@ namespace tut template<> template<> void object::test<5>() { - set_test_name("test leap.lua"); + set_test_name("leap.request() from main thread"); const std::string lua( - "-- test leap.lua\n" + "-- leap.request() from main thread\n" + "\n" + "leap = require 'leap'\n" + "\n" + "return {\n" + " a=leap.request('echo', {data='a'}).data,\n" + " b=leap.request('echo', {data='b'}).data\n" + "}\n" + ); + + LLEventStream pump("echo", false); + LLTempBoundListener conn{ + pump.listen( + "test<5>()", + listener([](const LLSD& data) + { + LL_DEBUGS("Lua") << "echo pump got: " << data << LL_ENDL; + LLEventPumps::instance().post( + data["reply"], + llsd::map("reqid", data["reqid"], "data", data["data"])); + })) + }; + + LuaState L; + auto [count, result] = LLLUAmanager::waitScriptLine(L, lua); + ensure_equals("Lua script didn't return item", count, 1); + ensure_equals("echo failed", result, llsd::map("a", "a", "b", "b")); + } + + template<> template<> + void object::test<6>() + { + set_test_name("interleave leap.request() responses"); + const std::string lua( + "-- interleave leap.request() responses\n" "\n" "fiber = require('fiber')\n" "leap = require('leap')\n" @@ -359,16 +393,13 @@ namespace tut "fiber.launch('catchall', drain, catchall)\n" "fiber.launch('catch_special', drain, catch_special)\n" "fiber.launch('requester(a)', requester, 'a')\n" - "-- requester(a)\n" "fiber.launch('requester(b)', requester, 'b')\n" - "fiber.run()\n" - "-- fiber.print_all()\n" ); LLSD requests; LLEventStream pump("testpump", false); LLTempBoundListener conn{ - pump.listen("test<5>()", + pump.listen("test<6>()", listener([&requests](const LLSD& data) { LL_DEBUGS("Lua") << "testpump got: " << data << LL_ENDL; -- cgit v1.2.3 From 93b30f960ea327fe3c36deed72a8075ce0d13f19 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 24 Mar 2024 02:16:55 +0900 Subject: Introduce LLStreamListener: bundle LLEventStream+LLTempBoundListener. This is a very common pattern, especially in test code, but elsewhere in the viewer too. Use it in llluamanager_test.cpp. --- indra/llcommon/llevents.h | 43 +++++++++++++++++++++++++ indra/newview/tests/llluamanager_test.cpp | 53 ++++++++++++------------------- 2 files changed, 64 insertions(+), 32 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 1f35a6de18..ebc893d1e6 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -151,6 +151,8 @@ typedef boost::signals2::signal LL /// Methods that forward listeners (e.g. constructed with /// boost::bind()) should accept (const LLEventListener&) typedef LLStandardSignal::slot_type LLEventListener; +/// Accept a void listener too +typedef std::function LLVoidListener; /// Result of registering a listener, supports connected(), /// disconnect() and blocked() typedef boost::signals2::connection LLBoundListener; @@ -158,6 +160,23 @@ typedef boost::signals2::connection LLBoundListener; /// referenced listener when the LLTempBoundListener instance is destroyed. typedef boost::signals2::scoped_connection LLTempBoundListener; +/// Accepting (const LLListener&) allows either LLEventListener or LLVoidListener +/// TODO: but compiler considers the constructor call ambiguous?? +class LLListener +{ +public: + LLListener(const LLEventListener& listener): + mListener(listener) + {} + LLListener(const LLVoidListener& listener): + mListener([listener](const LLSD& data){ listener(data); return false; }) + {} + operator LLEventListener() const { return mListener; } + +private: + LLEventListener mListener; +}; + /** * A common idiom for event-based code is to accept either a callable -- * directly called on completion -- or the string name of an LLEventPump on @@ -687,6 +706,30 @@ private: EventList mEventHistory; }; +/***************************************************************************** +* 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 LL_COMMON_API LLNamedListener: PUMP +{ + using pump_t = PUMP; +public: + template + LLNamedListener(const std::string& name, LISTENER&& listener): + pump_t(name, false), // don't tweak the name + mConn(pump_t::listen("func", std::forward(listener))) + {} + +private: + LLTempBoundListener mConn; +}; +using LLStreamListener = LLNamedListener<>; + /***************************************************************************** * LLReqID *****************************************************************************/ diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index 0fd91c1354..872d7827fe 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -105,10 +105,8 @@ namespace tut void from_lua(const std::string& desc, const std::string_view& construct, const LLSD& expect) { LLSD fromlua; - LLEventStream replypump("testpump"); - LLTempBoundListener conn( - replypump.listen("llluamanager_test", - listener([&fromlua](const LLSD& data){ fromlua = data; }))); + LLStreamListener pump("testpump", + listener([&fromlua](const LLSD& data){ fromlua = data; })); const std::string lua(stringize( "data = ", construct, "\n" "post_on('testpump', data)\n" @@ -137,11 +135,9 @@ namespace tut { set_test_name("test post_on(), get_event_pumps(), get_event_next()"); StringVec posts; - LLEventStream replypump("testpump"); - LLTempBoundListener conn( - replypump.listen("test<3>", - listener([&posts](const LLSD& data) - { posts.push_back(data.asString()); }))); + LLStreamListener pump("testpump", + listener([&posts](const LLSD& data) + { posts.push_back(data.asString()); })); const std::string lua( "-- test post_on,get_event_pumps,get_event_next\n" "post_on('testpump', 'entry')\n" @@ -180,7 +176,7 @@ namespace tut void round_trip(const std::string& desc, const LLSD& send, const LLSD& expect) { - LLEventMailDrop replypump("testpump"); + LLEventMailDrop testpump("testpump"); const std::string lua( "-- test LLSD round trip\n" "replypump, cmdpump = get_event_pumps()\n" @@ -194,7 +190,7 @@ namespace tut // reached the get_event_next() call, which suspends the calling C++ // coroutine (including the Lua code running on it) until we post // something to that reply pump. - auto luapump{ llcoro::suspendUntilEventOn(replypump).asString() }; + auto luapump{ llcoro::suspendUntilEventOn(testpump).asString() }; LLEventPumps::instance().post(luapump, send); // The C++ coroutine running the Lua script is now ready to run. Run // it so it will echo the LLSD back to us. @@ -319,18 +315,13 @@ namespace tut "}\n" ); - LLEventStream pump("echo", false); - LLTempBoundListener conn{ - pump.listen( - "test<5>()", - listener([](const LLSD& data) - { - LL_DEBUGS("Lua") << "echo pump got: " << data << LL_ENDL; - LLEventPumps::instance().post( - data["reply"], - llsd::map("reqid", data["reqid"], "data", data["data"])); - })) - }; + LLStreamListener pump( + "echo", + listener([](const LLSD& data) + { + LL_DEBUGS("Lua") << "echo pump got: " << data << LL_ENDL; + sendReply(data, data); + })); LuaState L; auto [count, result] = LLLUAmanager::waitScriptLine(L, lua); @@ -397,15 +388,13 @@ namespace tut ); LLSD requests; - LLEventStream pump("testpump", false); - LLTempBoundListener conn{ - pump.listen("test<6>()", - listener([&requests](const LLSD& data) - { - LL_DEBUGS("Lua") << "testpump got: " << data << LL_ENDL; - requests.append(data); - })) - }; + LLStreamListener pump( + "testpump", + listener([&requests](const LLSD& data) + { + LL_DEBUGS("Lua") << "testpump got: " << data << LL_ENDL; + requests.append(data); + })); LuaState L; auto future = LLLUAmanager::startScriptLine(L, lua); -- cgit v1.2.3 From 41e14d35ae2dfa644716cb195545d59c468538c5 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Mon, 25 Mar 2024 15:06:11 +0200 Subject: Add keystroke event support and allow adding text lines to the line editor --- indra/llui/llluafloater.cpp | 40 ++++++++++++++++++---- indra/newview/scripts/lua/luafloater_demo.xml | 13 ++++++- indra/newview/scripts/lua/test_luafloater_demo.lua | 4 +-- .../scripts/lua/test_luafloater_gesture_list.lua | 3 +- indra/newview/scripts/lua/util.lua | 10 ------ 5 files changed, 48 insertions(+), 22 deletions(-) (limited to 'indra') diff --git a/indra/llui/llluafloater.cpp b/indra/llui/llluafloater.cpp index afc287a864..8f624788c2 100644 --- a/indra/llui/llluafloater.cpp +++ b/indra/llui/llluafloater.cpp @@ -32,6 +32,7 @@ #include "llcheckboxctrl.h" #include "llcombobox.h" #include "llscrolllistctrl.h" +#include "lltexteditor.h" const std::string LISTENER_NAME("LLLuaFloater"); @@ -45,7 +46,8 @@ std::set EVENT_LIST = { "right_mouse_down", "right_mouse_up", "post_build", - "floater_close" + "floater_close", + "keystroke" }; LLLuaFloater::LLLuaFloater(const LLSD &key) : @@ -99,6 +101,16 @@ LLLuaFloater::LLLuaFloater(const LLSD &key) : } }, requiredParams); + mDispatchListener.add("add_text", "", [this](const LLSD &event) + { + LLTextEditor *editor = getChild(event["ctrl_name"].asString()); + if (editor) + { + editor->pasteTextWithLinebreaks(stringize(event["value"])); + editor->addLineBreakChar(true); + } + }, requiredParams); + mDispatchListener.add("set_title", "", [this](const LLSD &event) { setTitle(event["value"].asString()); @@ -182,6 +194,12 @@ void LLLuaFloater::registerCallback(const std::string &ctrl_name, const std::str post(event.with("x", x).with("y", y)); }; + auto post_with_value = [this, data](LLSD& value) + { + LLSD event(data); + post(event.with("value", value)); + }; + if (event_is(event, "mouse_enter")) { ctrl->setMouseEnterCallback(mouse_event_cb); @@ -211,18 +229,26 @@ void LLLuaFloater::registerCallback(const std::string &ctrl_name, const std::str LLScrollListCtrl *list = dynamic_cast(ctrl); if (list) { - list->setDoubleClickCallback( - [this, data, list]() - { - LLSD event(data); - post(event.with("value", list->getCurrentID())); - }); + list->setDoubleClickCallback( [this, post_with_value, list](){ post_with_value(LLSD(list->getCurrentID())); }); } else { ctrl->setDoubleClickCallback(mouse_event_coords_cb); } } + else if (event_is(event, "keystroke")) + { + LLTextEditor* text_editor = dynamic_cast(ctrl); + if (text_editor) + { + text_editor->setKeystrokeCallback([this, post_with_value](LLTextEditor *editor) { post_with_value(editor->getValue()); }); + } + LLLineEditor* line_editor = dynamic_cast(ctrl); + if (line_editor) + { + line_editor->setKeystrokeCallback([this, post_with_value](LLLineEditor *editor, void* userdata) { post_with_value(editor->getValue()); }, NULL); + } + } else { LL_WARNS("LuaFloater") << "Can't register callback for unknown event: " << event << " , control: " << ctrl_name << LL_ENDL; diff --git a/indra/newview/scripts/lua/luafloater_demo.xml b/indra/newview/scripts/lua/luafloater_demo.xml index 069f229128..b2273d7718 100644 --- a/indra/newview/scripts/lua/luafloater_demo.xml +++ b/indra/newview/scripts/lua/luafloater_demo.xml @@ -1,7 +1,7 @@ + diff --git a/indra/newview/scripts/lua/test_luafloater_demo.lua b/indra/newview/scripts/lua/test_luafloater_demo.lua index b81259c060..22ed7d7b3a 100644 --- a/indra/newview/scripts/lua/test_luafloater_demo.lua +++ b/indra/newview/scripts/lua/test_luafloater_demo.lua @@ -2,7 +2,6 @@ XML_FILE_PATH = "luafloater_demo.xml" leap = require 'leap' coro = require 'coro' -util = require 'util' --event pump for sending actions to the floater COMMAND_PUMP_NAME = "" @@ -15,7 +14,7 @@ end) leap.process() local function _event(event_name) - if not util.contains(event_list, event_name) then + if not table.find(event_list, event_name) then print_warning("Incorrect event name: " .. event_name) end return event_name @@ -31,6 +30,7 @@ function getCurrentTime() end function handleEvents(event_data) + post({action="add_text", ctrl_name="events_editor", value = event_data}) if event_data.event == _event("commit") then if event_data.ctrl_name == "disable_ctrl" then post({action="set_enabled", ctrl_name="open_btn", value = (1 - event_data.value)}) diff --git a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua index b46e36b4d9..b1ff129d85 100644 --- a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua +++ b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua @@ -2,7 +2,6 @@ XML_FILE_PATH = "luafloater_gesture_list.xml" leap = require 'leap' coro = require 'coro' -util = require 'util' LLGesture = require 'LLGesture' --event pump for sending actions to the floater @@ -16,7 +15,7 @@ end) leap.process() local function _event(event_name) - if not util.contains(event_list, event_name) then + if not table.find(event_list, event_name) then print_warning("Incorrect event name: " .. event_name) end return event_name diff --git a/indra/newview/scripts/lua/util.lua b/indra/newview/scripts/lua/util.lua index 5d6042dfe5..e3af633ea7 100644 --- a/indra/newview/scripts/lua/util.lua +++ b/indra/newview/scripts/lua/util.lua @@ -36,14 +36,4 @@ function util.equal(t1, t2) return util.empty(temp) end --- check if array-like table contains certain value -function util.contains(t, v) - for _, value in ipairs(t) do - if value == v then - return true - end - end - return false -end - return util -- cgit v1.2.3 From fd8c5fced1ee62e08c55adf92fb9c8d0e52d313a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 25 Mar 2024 10:03:17 -0400 Subject: Remove colliding LLListener. --- indra/llcommon/llevents.h | 17 ----------------- 1 file changed, 17 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index ebc893d1e6..77a405871d 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -160,23 +160,6 @@ typedef boost::signals2::connection LLBoundListener; /// referenced listener when the LLTempBoundListener instance is destroyed. typedef boost::signals2::scoped_connection LLTempBoundListener; -/// Accepting (const LLListener&) allows either LLEventListener or LLVoidListener -/// TODO: but compiler considers the constructor call ambiguous?? -class LLListener -{ -public: - LLListener(const LLEventListener& listener): - mListener(listener) - {} - LLListener(const LLVoidListener& listener): - mListener([listener](const LLSD& data){ listener(data); return false; }) - {} - operator LLEventListener() const { return mListener; } - -private: - LLEventListener mListener; -}; - /** * A common idiom for event-based code is to accept either a callable -- * directly called on completion -- or the string name of an LLEventPump on -- cgit v1.2.3 From 7f39a5bb109338b201c9cd0d3a40baac2b2e4fc1 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Mon, 25 Mar 2024 17:10:46 +0200 Subject: mac build fix --- indra/llui/llluafloater.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/llui/llluafloater.cpp b/indra/llui/llluafloater.cpp index 8f624788c2..268075b05d 100644 --- a/indra/llui/llluafloater.cpp +++ b/indra/llui/llluafloater.cpp @@ -194,7 +194,7 @@ void LLLuaFloater::registerCallback(const std::string &ctrl_name, const std::str post(event.with("x", x).with("y", y)); }; - auto post_with_value = [this, data](LLSD& value) + auto post_with_value = [this, data](LLSD value) { LLSD event(data); post(event.with("value", value)); @@ -229,7 +229,7 @@ void LLLuaFloater::registerCallback(const std::string &ctrl_name, const std::str LLScrollListCtrl *list = dynamic_cast(ctrl); if (list) { - list->setDoubleClickCallback( [this, post_with_value, list](){ post_with_value(LLSD(list->getCurrentID())); }); + list->setDoubleClickCallback( [post_with_value, list](){ post_with_value(LLSD(list->getCurrentID())); }); } else { @@ -241,12 +241,12 @@ void LLLuaFloater::registerCallback(const std::string &ctrl_name, const std::str LLTextEditor* text_editor = dynamic_cast(ctrl); if (text_editor) { - text_editor->setKeystrokeCallback([this, post_with_value](LLTextEditor *editor) { post_with_value(editor->getValue()); }); + text_editor->setKeystrokeCallback([post_with_value](LLTextEditor *editor) { post_with_value(editor->getValue()); }); } LLLineEditor* line_editor = dynamic_cast(ctrl); if (line_editor) { - line_editor->setKeystrokeCallback([this, post_with_value](LLLineEditor *editor, void* userdata) { post_with_value(editor->getValue()); }, NULL); + line_editor->setKeystrokeCallback([post_with_value](LLLineEditor *editor, void* userdata) { post_with_value(editor->getValue()); }, NULL); } } else -- cgit v1.2.3 From 6cedaecad9c4830aa29068b90611b8b9da301ac9 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Mon, 25 Mar 2024 20:41:33 +0200 Subject: Update test scripts to call leap.request() from main thread --- indra/newview/scripts/lua/test_luafloater_demo.lua | 16 ++-------------- .../newview/scripts/lua/test_luafloater_gesture_list.lua | 14 ++------------ 2 files changed, 4 insertions(+), 26 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/test_luafloater_demo.lua b/indra/newview/scripts/lua/test_luafloater_demo.lua index 22ed7d7b3a..f2cf34206e 100644 --- a/indra/newview/scripts/lua/test_luafloater_demo.lua +++ b/indra/newview/scripts/lua/test_luafloater_demo.lua @@ -6,12 +6,7 @@ coro = require 'coro' --event pump for sending actions to the floater COMMAND_PUMP_NAME = "" --table of floater UI events -event_list={} -coro.launch(function () - event_list = leap.request("LLFloaterReg", {op="getFloaterEvents"})["events"] - leap.done() -end) -leap.process() +event_list=leap.request("LLFloaterReg", {op="getFloaterEvents"}).events local function _event(event_name) if not table.find(event_list, event_name) then @@ -53,12 +48,7 @@ end local key = {xml_path = XML_FILE_PATH, op = "showLuaFloater"} --sign for additional events for defined control {= {action1, action2, ...}} key.extra_events={show_time_lbl = {_event("right_mouse_down"), _event("double_click")}} -coro.launch(function () - --script received event pump name, after floater was built - COMMAND_PUMP_NAME = leap.request("LLFloaterReg", key)["command_name"] - leap.done() -end) -leap.process() +COMMAND_PUMP_NAME = leap.request("LLFloaterReg", key).command_name catch_events = leap.WaitFor:new(-1, "all_events") function catch_events:filter(pump, data) @@ -74,5 +64,3 @@ function process_events(waitfor) end coro.launch(process_events, catch_events) -leap.process() -print_warning("End of the script") diff --git a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua index b1ff129d85..049ba757d3 100644 --- a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua +++ b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua @@ -7,12 +7,7 @@ LLGesture = require 'LLGesture' --event pump for sending actions to the floater COMMAND_PUMP_NAME = "" --table of floater UI events -event_list={} -coro.launch(function () - event_list = leap.request("LLFloaterReg", {op="getFloaterEvents"})["events"] - leap.done() -end) -leap.process() +event_list=leap.request("LLFloaterReg", {op="getFloaterEvents"}).events local function _event(event_name) if not table.find(event_list, event_name) then @@ -50,11 +45,7 @@ end local key = {xml_path = XML_FILE_PATH, op = "showLuaFloater"} --receive additional events for defined control {= {action1, action2, ...}} key.extra_events={gesture_list = {_event("double_click")}} -coro.launch(function () - handleEvents(leap.request("LLFloaterReg", key)) - leap.done() -end) -leap.process() +handleEvents(leap.request("LLFloaterReg", key)) catch_events = leap.WaitFor:new(-1, "all_events") function catch_events:filter(pump, data) @@ -70,4 +61,3 @@ function process_events(waitfor) end coro.launch(process_events, catch_events) -leap.process() -- cgit v1.2.3 From ac4fa418e3a7402f9d9122c726d2fbfc4b8767b2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 25 Mar 2024 16:29:17 -0400 Subject: Add LL. prefix to viewer entry points, fix existing references. --- indra/llcommon/lua_function.cpp | 13 +++++++++++- indra/newview/scripts/lua/WaitQueue.lua | 2 +- indra/newview/scripts/lua/leap.lua | 10 ++++----- indra/newview/scripts/lua/test_LLGesture.lua | 2 +- indra/newview/scripts/lua/test_luafloater_demo.lua | 6 +++--- .../scripts/lua/test_luafloater_gesture_list.lua | 2 +- indra/newview/tests/llluamanager_test.cpp | 24 +++++++++++----------- 7 files changed, 35 insertions(+), 24 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 441e17dafd..962e9ee2fa 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -439,6 +439,8 @@ void LuaState::initLuaState() LuaFunction::init(mState); // Try to make print() write to our log. lua_register(mState, "print", LuaFunction::get("print_info")); + // We don't want to have to prefix require(). + lua_register(mState, "require", LuaFunction::get("require")); } LuaState::~LuaState() @@ -646,11 +648,20 @@ LuaFunction::LuaFunction(const std::string_view& name, lua_CFunction function, void LuaFunction::init(lua_State* L) { const auto& [registry, lookup] = getRState(); + luaL_checkstack(L, 2, nullptr); + // create LL table -- + // it happens that we know exactly how many non-array members we want + lua_createtable(L, 0, int(narrow(lookup.size()))); + int idx = lua_gettop(L); for (const auto& [name, pair]: registry) { const auto& [funcptr, helptext] = pair; - lua_register(L, name.c_str(), funcptr); + // store funcptr in LL table with saved name + lua_pushcfunction(L, funcptr, name.c_str()); + lua_setfield(L, idx, name.c_str()); } + // store LL in new lua_State's globals + lua_setglobal(L, "LL"); } lua_CFunction LuaFunction::get(const std::string& key) diff --git a/indra/newview/scripts/lua/WaitQueue.lua b/indra/newview/scripts/lua/WaitQueue.lua index a34dbef4d7..1fbcc50847 100644 --- a/indra/newview/scripts/lua/WaitQueue.lua +++ b/indra/newview/scripts/lua/WaitQueue.lua @@ -5,7 +5,7 @@ local fiber = require('fiber') local Queue = require('Queue') --- local debug = print_debug +-- local debug = LL.print_debug local function debug(...) end local WaitQueue = Queue:new() diff --git a/indra/newview/scripts/lua/leap.lua b/indra/newview/scripts/lua/leap.lua index 77f3a3e116..a60819d493 100644 --- a/indra/newview/scripts/lua/leap.lua +++ b/indra/newview/scripts/lua/leap.lua @@ -51,7 +51,7 @@ local leap = {} -- _command: string name of command LLEventPump. post_to(_command, ...) -- engages LLLeapListener operations such as listening on a specified other -- LLEventPump, etc. -leap._reply, leap._command = get_event_pumps() +leap._reply, leap._command = LL.get_event_pumps() -- Dict of features added to the LEAP protocol since baseline implementation. -- Before engaging a new feature that might break an older viewer, we can -- check for the presence of that feature key. This table is solely about the @@ -112,7 +112,7 @@ function leap.send(pump, data, reqid) end end debug('leap.send(%s, %s) calling post_on()', pump, data) - post_on(pump, data) + LL.post_on(pump, data) end -- common setup code shared by request() and generate() @@ -215,7 +215,7 @@ local function unsolicited(pump, data) return end end - print_debug(string.format('unsolicited(%s, %s) discarding unclaimed event', pump, data)) + LL.print_debug(string.format('unsolicited(%s, %s) discarding unclaimed event', pump, data)) end -- Route incoming (pump, data) event to the appropriate waiting coroutine. @@ -244,7 +244,7 @@ fiber.set_idle(function () return 'done' end debug('leap.idle() calling get_event_next()') - local ok, pump, data = pcall(get_event_next) + local ok, pump, data = pcall(LL.get_event_next) debug('leap.idle() got %s: %s, %s', ok, pump, data) -- ok false means get_event_next() raised a Lua error, pump is message if not ok then @@ -414,7 +414,7 @@ end -- called by leap.process() when get_event_next() raises an error function leap.WaitFor:exception(message) - print_warning(self.name .. ' error: ' .. message) + LL.print_warning(self.name .. ' error: ' .. message) self._queue:Error(message) end diff --git a/indra/newview/scripts/lua/test_LLGesture.lua b/indra/newview/scripts/lua/test_LLGesture.lua index 5c0db6c063..5897a0e3cb 100644 --- a/indra/newview/scripts/lua/test_LLGesture.lua +++ b/indra/newview/scripts/lua/test_LLGesture.lua @@ -22,7 +22,7 @@ coro.launch(function() print(name) LLGesture.startGesture(uuid) repeat - sleep(1) + LL.sleep(1) until not LLGesture.isGesturePlaying(uuid) end print('Done.') diff --git a/indra/newview/scripts/lua/test_luafloater_demo.lua b/indra/newview/scripts/lua/test_luafloater_demo.lua index b81259c060..4e071e4abe 100644 --- a/indra/newview/scripts/lua/test_luafloater_demo.lua +++ b/indra/newview/scripts/lua/test_luafloater_demo.lua @@ -16,7 +16,7 @@ leap.process() local function _event(event_name) if not util.contains(event_list, event_name) then - print_warning("Incorrect event name: " .. event_name) + LL.print_warning("Incorrect event name: " .. event_name) end return event_name end @@ -45,7 +45,7 @@ function handleEvents(event_data) post({action="set_value", ctrl_name="time_lbl", value= getCurrentTime()}) end elseif event_data.event == _event("floater_close") then - print_warning("Floater was closed") + LL.print_warning("Floater was closed") leap.done() end end @@ -75,4 +75,4 @@ end coro.launch(process_events, catch_events) leap.process() -print_warning("End of the script") +LL.print_warning("End of the script") diff --git a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua index b46e36b4d9..9c718c353b 100644 --- a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua +++ b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua @@ -17,7 +17,7 @@ leap.process() local function _event(event_name) if not util.contains(event_list, event_name) then - print_warning("Incorrect event name: " .. event_name) + LL.print_warning("Incorrect event name: " .. event_name) end return event_name end diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index 872d7827fe..e10dedcf53 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -109,7 +109,7 @@ namespace tut listener([&fromlua](const LLSD& data){ fromlua = data; })); const std::string lua(stringize( "data = ", construct, "\n" - "post_on('testpump', data)\n" + "LL.post_on('testpump', data)\n" )); LuaState L; auto [count, result] = LLLUAmanager::waitScriptLine(L, lua); @@ -140,14 +140,14 @@ namespace tut { posts.push_back(data.asString()); })); const std::string lua( "-- test post_on,get_event_pumps,get_event_next\n" - "post_on('testpump', 'entry')\n" - "post_on('testpump', 'get_event_pumps()')\n" - "replypump, cmdpump = get_event_pumps()\n" - "post_on('testpump', replypump)\n" - "post_on('testpump', 'get_event_next()')\n" - "pump, data = get_event_next()\n" - "post_on('testpump', data)\n" - "post_on('testpump', 'exit')\n" + "LL.post_on('testpump', 'entry')\n" + "LL.post_on('testpump', 'get_event_pumps()')\n" + "replypump, cmdpump = LL.get_event_pumps()\n" + "LL.post_on('testpump', replypump)\n" + "LL.post_on('testpump', 'get_event_next()')\n" + "pump, data = LL.get_event_next()\n" + "LL.post_on('testpump', data)\n" + "LL.post_on('testpump', 'exit')\n" ); LuaState L; // It's important to let the startScriptLine() coroutine run @@ -179,9 +179,9 @@ namespace tut LLEventMailDrop testpump("testpump"); const std::string lua( "-- test LLSD round trip\n" - "replypump, cmdpump = get_event_pumps()\n" - "post_on('testpump', replypump)\n" - "pump, data = get_event_next()\n" + "replypump, cmdpump = LL.get_event_pumps()\n" + "LL.post_on('testpump', replypump)\n" + "pump, data = LL.get_event_next()\n" "return data\n" ); LuaState L; -- cgit v1.2.3 From 98e6356aed0c757f16267cc2ae921f9c90a249fe Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 25 Mar 2024 16:57:28 -0400 Subject: Add LL.check_stop() entry point and call it in fiber scheduler(). fiber.lua's scheduler() is greedy, in the sense that it wants to run every ready Lua fiber before retrieving the next incoming event from the viewer (and possibly blocking for some real time before it becomes available). But check for viewer shutdown before resuming any suspended-but-ready Lua fiber. --- indra/llcommon/lua_function.cpp | 9 +++++++++ indra/newview/scripts/lua/fiber.lua | 4 +--- 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 962e9ee2fa..9f0abd5674 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -681,6 +681,15 @@ std::pair LuaFunction::getState() return { registry, lookup }; } +/***************************************************************************** +* check_stop() +*****************************************************************************/ +lua_function(check_stop, "ensure that a Lua script responds to viewer shutdown") +{ + LLCoros::checkStop(); + return 0; +} + /***************************************************************************** * help() *****************************************************************************/ diff --git a/indra/newview/scripts/lua/fiber.lua b/indra/newview/scripts/lua/fiber.lua index 7dc67f510c..aebf27357f 100644 --- a/indra/newview/scripts/lua/fiber.lua +++ b/indra/newview/scripts/lua/fiber.lua @@ -222,8 +222,6 @@ local function scheduler() -- processing to the main thread. If called from a coroutine, pass control -- back to the main thread. if coroutine.running() then - -- seize the opportunity to make sure the viewer isn't shutting down --- check_stop() -- this is a real coroutine, yield normally to main thread coroutine.yield() -- main certainly still exists @@ -240,7 +238,7 @@ local function scheduler() repeat for co in live_ready_iter do -- seize the opportunity to make sure the viewer isn't shutting down --- check_stop() + LL.check_stop() -- before we re-append co, is it the only remaining entry? others = next(ready) -- co is live, re-append it to the ready list -- cgit v1.2.3 From 5b04ae7812e6cb0d0c9895aec6db6a206d4e09e8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 25 Mar 2024 17:35:28 -0400 Subject: util.lua claims functions are in alpha order - make it so. Also streamline util.contains(), given table.find(). --- indra/newview/scripts/lua/util.lua | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/util.lua b/indra/newview/scripts/lua/util.lua index 5d6042dfe5..a2191288f6 100644 --- a/indra/newview/scripts/lua/util.lua +++ b/indra/newview/scripts/lua/util.lua @@ -2,9 +2,9 @@ local util = {} --- cheap test whether table t is empty -function util.empty(t) - return not next(t) +-- check if array-like table contains certain value +function util.contains(t, v) + return table.find(t, v) ~= nil end -- reliable count of the number of entries in table t @@ -17,6 +17,11 @@ function util.count(t) return count end +-- cheap test whether table t is empty +function util.empty(t) + return not next(t) +end + -- recursive table equality function util.equal(t1, t2) if not (type(t1) == 'table' and type(t2) == 'table') then @@ -36,14 +41,4 @@ function util.equal(t1, t2) return util.empty(temp) end --- check if array-like table contains certain value -function util.contains(t, v) - for _, value in ipairs(t) do - if value == v then - return true - end - end - return false -end - return util -- cgit v1.2.3 From 24a7842b9b08dc5cfb89fe36e5ebcd9c2598fa44 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Tue, 26 Mar 2024 19:12:13 +0200 Subject: update scripts to use fiber.launch() --- indra/newview/scripts/lua/test_luafloater_demo.lua | 3 ++- indra/newview/scripts/lua/test_luafloater_gesture_list.lua | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/test_luafloater_demo.lua b/indra/newview/scripts/lua/test_luafloater_demo.lua index f2cf34206e..be189bc146 100644 --- a/indra/newview/scripts/lua/test_luafloater_demo.lua +++ b/indra/newview/scripts/lua/test_luafloater_demo.lua @@ -2,6 +2,7 @@ XML_FILE_PATH = "luafloater_demo.xml" leap = require 'leap' coro = require 'coro' +fiber = require 'fiber' --event pump for sending actions to the floater COMMAND_PUMP_NAME = "" @@ -63,4 +64,4 @@ function process_events(waitfor) end end -coro.launch(process_events, catch_events) +fiber.launch("catch_events", process_events, catch_events) diff --git a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua index 049ba757d3..7e2139468f 100644 --- a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua +++ b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua @@ -2,6 +2,7 @@ XML_FILE_PATH = "luafloater_gesture_list.xml" leap = require 'leap' coro = require 'coro' +fiber = require 'fiber' LLGesture = require 'LLGesture' --event pump for sending actions to the floater @@ -60,4 +61,4 @@ function process_events(waitfor) end end -coro.launch(process_events, catch_events) +fiber.launch("catch_events", process_events, catch_events) -- cgit v1.2.3 From c618758c7c91917905b1075e29944ef70e7e9b33 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 27 Mar 2024 15:53:48 -0400 Subject: Run loaded `require()` module on Lua's main thread. The problem with running a `require()` module on a Lua coroutine is that it prohibits calling `leap.request()` at module load time. When a coroutine calls `leap.request()`, it must yield back to Lua's main thread -- but a `require()` module is forbidden from yielding. Running on Lua's main thread means that (after potentially giving time slices to other ready coroutines) `fiber.lua` will request the response event from the viewer, and continue processing the loaded module without having to yield. --- indra/newview/llluamanager.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) (limited to 'indra') diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index c65f062fd7..aed6aee239 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -508,12 +508,14 @@ void LLRequireResolver::runModule(const std::string& desc, const std::string& co // Module needs to run in a new thread, isolated from the rest. // Note: we create ML on main thread so that it doesn't inherit environment of L. lua_State *GL = lua_mainthread(L); - lua_State *ML = lua_newthread(GL); +// lua_State *ML = lua_newthread(GL); + // Try loading modules on Lua's main thread instead. + lua_State *ML = GL; // lua_newthread() pushed the new thread object on GL's stack. Move to L's. - lua_xmove(GL, L, 1); +// lua_xmove(GL, L, 1); // new thread needs to have the globals sandboxed - luaL_sandboxthread(ML); +// luaL_sandboxthread(ML); { // If loadstring() returns (! LUA_OK) then there's an error message on @@ -523,7 +525,9 @@ void LLRequireResolver::runModule(const std::string& desc, const std::string& co { // luau uses Lua 5.3's version of lua_resume(): // run the coroutine on ML, "from" L, passing no arguments. - int status = lua_resume(ML, L, 0); +// int status = lua_resume(ML, L, 0); + // we expect one return value + int status = lua_pcall(ML, 0, 1, 0); if (status == LUA_OK) { @@ -545,9 +549,12 @@ void LLRequireResolver::runModule(const std::string& desc, const std::string& co } // There's now a return value (string error message or module) on top of ML. // Move return value to L's stack. - lua_xmove(ML, L, 1); + if (ML != L) + { + lua_xmove(ML, L, 1); + } // remove ML from L's stack - lua_remove(L, -2); +// lua_remove(L, -2); // // DON'T call lua_close(ML)! Since ML is only a thread of L, corrupts L too! // lua_close(ML); } -- cgit v1.2.3 From 58d5e288e0bfaa9819b68b376767a8a39a97fef8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 27 Mar 2024 16:20:36 -0400 Subject: poetry --- indra/newview/scripts/lua/Queue.lua | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'indra') diff --git a/indra/newview/scripts/lua/Queue.lua b/indra/newview/scripts/lua/Queue.lua index b0a5a87f87..5ab2a8a72c 100644 --- a/indra/newview/scripts/lua/Queue.lua +++ b/indra/newview/scripts/lua/Queue.lua @@ -1,6 +1,12 @@ -- from https://create.roblox.com/docs/luau/queues#implementing-queues, -- amended per https://www.lua.org/pil/16.1.html +-- While coding some scripting in Lua +-- I found that I needed a queua +-- I thought of linked list +-- But had to resist +-- For fear it might be too obscua. + local Queue = {} function Queue:new() -- cgit v1.2.3 From af38e606865c2ed49a13bd6bd91d9604997798c8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 27 Mar 2024 16:38:45 -0400 Subject: Enhance Lua debugging output. Don't use "debug" as the name of a function to conditionally write debug messages: "debug" is a Luau built-in library, and assigning that name locally would shadow the builtin. Use "dbg" instead. Recast fiber.print_all() as fiber.format_all() that returns a string; then print_all() is simply print(format_all()). This refactoring allows us to use dbg(format_all()) as well. Add a couple new dbg() messages at fiber state changes. --- indra/newview/scripts/lua/ErrorQueue.lua | 8 +++--- indra/newview/scripts/lua/WaitQueue.lua | 11 ++++---- indra/newview/scripts/lua/fiber.lua | 45 +++++++++++++++++++------------- indra/newview/scripts/lua/leap.lua | 26 +++++++++--------- 4 files changed, 50 insertions(+), 40 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/ErrorQueue.lua b/indra/newview/scripts/lua/ErrorQueue.lua index 076742815a..6ed1c10d5c 100644 --- a/indra/newview/scripts/lua/ErrorQueue.lua +++ b/indra/newview/scripts/lua/ErrorQueue.lua @@ -3,22 +3,22 @@ -- raise that error. local WaitQueue = require('WaitQueue') --- local debug = require('printf') -local function debug(...) end +-- local dbg = require('printf') +local function dbg(...) end local ErrorQueue = WaitQueue:new() function ErrorQueue:Error(message) -- Setting Error() is a marker, like closing the queue. Once we reach the -- error, every subsequent Dequeue() call will raise the same error. - debug('Setting self._closed to %q', message) + dbg('Setting self._closed to %q', message) self._closed = message self:_wake_waiters() end function ErrorQueue:Dequeue() local value = WaitQueue.Dequeue(self) - debug('ErrorQueue:Dequeue: base Dequeue() got %s', value) + dbg('ErrorQueue:Dequeue: base Dequeue() got %s', value) if value ~= nil then -- queue not yet closed, show caller return value diff --git a/indra/newview/scripts/lua/WaitQueue.lua b/indra/newview/scripts/lua/WaitQueue.lua index 1fbcc50847..ad4fdecf43 100644 --- a/indra/newview/scripts/lua/WaitQueue.lua +++ b/indra/newview/scripts/lua/WaitQueue.lua @@ -5,8 +5,8 @@ local fiber = require('fiber') local Queue = require('Queue') --- local debug = LL.print_debug -local function debug(...) end +-- local dbg = require('printf') +local function dbg(...) end local WaitQueue = Queue:new() @@ -60,17 +60,18 @@ function WaitQueue:Dequeue() -- the queue while there are still items left, and we want the -- consumer(s) to retrieve those last few items. if self._closed then - debug('WaitQueue:Dequeue(): closed') + dbg('WaitQueue:Dequeue(): closed') return nil end - debug('WaitQueue:Dequeue(): waiting') + dbg('WaitQueue:Dequeue(): waiting') -- add the running coroutine to the list of waiters + dbg('WaitQueue:Dequeue() running %s', tostring(coroutine.running() or 'main')) table.insert(self._waiters, fiber.running()) -- then let somebody else run fiber.wait() end -- here we're sure this queue isn't empty - debug('WaitQueue:Dequeue() calling Queue.Dequeue()') + dbg('WaitQueue:Dequeue() calling Queue.Dequeue()') return Queue.Dequeue(self) end diff --git a/indra/newview/scripts/lua/fiber.lua b/indra/newview/scripts/lua/fiber.lua index aebf27357f..9057e6c890 100644 --- a/indra/newview/scripts/lua/fiber.lua +++ b/indra/newview/scripts/lua/fiber.lua @@ -17,8 +17,8 @@ -- or with an error). local printf = require 'printf' --- local debug = printf -local function debug(...) end +-- local dbg = printf +local function dbg(...) end local coro = require 'coro' local fiber = {} @@ -78,22 +78,28 @@ function fiber.launch(name, func, ...) byname[namekey] = co -- and remember it as this fiber's name names[co] = namekey --- debug('launch(%s)', namekey) --- debug('byname[%s] = %s', namekey, tostring(byname[namekey])) --- debug('names[%s] = %s', tostring(co), names[co]) --- debug('ready[-1] = %s', tostring(ready[#ready])) +-- dbg('launch(%s)', namekey) +-- dbg('byname[%s] = %s', namekey, tostring(byname[namekey])) +-- dbg('names[%s] = %s', tostring(co), names[co]) +-- dbg('ready[-1] = %s', tostring(ready[#ready])) end -- for debugging -function fiber.print_all() - print('Ready fibers:' .. if next(ready) then '' else ' none') +function format_all() + output = {} + table.insert(output, 'Ready fibers:' .. if next(ready) then '' else ' none') for _, co in pairs(ready) do - printf(' %s: %s', fiber.get_name(co), fiber.status(co)) + table.insert(output, string.format(' %s: %s', fiber.get_name(co), fiber.status(co))) end - print('Waiting fibers:' .. if next(waiting) then '' else ' none') + table.insert(output, 'Waiting fibers:' .. if next(waiting) then '' else ' none') for co in pairs(waiting) do - printf(' %s: %s', fiber.get_name(co), fiber.status(co)) + table.insert(output, string.format(' %s: %s', fiber.get_name(co), fiber.status(co))) end + return table.concat(output, '\n') +end + +function fiber.print_all() + print(format_all()) end -- return either the running coroutine or, if called from the main thread, @@ -160,6 +166,7 @@ end -- Suspend the current fiber until some other fiber calls fiber.wake() on it function fiber.wait() + dbg('Fiber %q waiting', fiber.get_name()) set_waiting() -- now yield to other fibers fiber.yield() @@ -175,26 +182,27 @@ function fiber.wake(co) waiting[co] = nil -- add to end of ready list table.insert(ready, co) + dbg('Fiber %q ready', fiber.get_name(co)) -- but don't yet resume it: that happens next time we reach yield() end -- pop and return the next not-dead fiber in the ready list, or nil if none remain local function live_ready_iter() - -- don't write + -- don't write: -- for co in table.remove, ready, 1 -- because it would keep passing a new second parameter! for co in function() return table.remove(ready, 1) end do - debug('%s live_ready_iter() sees %s, status %s', + dbg('%s live_ready_iter() sees %s, status %s', fiber.get_name(), fiber.get_name(co), fiber.status(co)) -- keep removing the head entry until we find one that's not dead, -- discarding any dead coroutines along the way if co == 'main' or coroutine.status(co) ~= 'dead' then - debug('%s live_ready_iter() returning %s', + dbg('%s live_ready_iter() returning %s', fiber.get_name(), fiber.get_name(co)) return co end end - debug('%s live_ready_iter() returning nil', fiber.get_name()) + dbg('%s live_ready_iter() returning nil', fiber.get_name()) return nil end @@ -214,6 +222,7 @@ end -- * false, nil if this is the only remaining fiber -- * nil, x if configured idle() callback returns non-nil x local function scheduler() + dbg('scheduler():\n%s', format_all()) -- scheduler() is asymmetric because Lua distinguishes the main thread -- from other coroutines. The main thread can't yield; it can only resume -- other coroutines. So although an arbitrary coroutine could resume still @@ -311,12 +320,12 @@ function fiber.run() end local others, idle_done repeat - debug('%s calling fiber.run() calling scheduler()', fiber.get_name()) + dbg('%s calling fiber.run() calling scheduler()', fiber.get_name()) others, idle_done = scheduler() - debug("%s fiber.run()'s scheduler() returned %s, %s", fiber.get_name(), + dbg("%s fiber.run()'s scheduler() returned %s, %s", fiber.get_name(), tostring(others), tostring(idle_done)) until (not others) - debug('%s fiber.run() done', fiber.get_name()) + dbg('%s fiber.run() done', fiber.get_name()) -- For whatever it's worth, put our own fiber back in the ready list. table.insert(ready, fiber.running()) -- Once there are no more waiting fibers, and the only ready fiber is diff --git a/indra/newview/scripts/lua/leap.lua b/indra/newview/scripts/lua/leap.lua index a60819d493..9da1839c68 100644 --- a/indra/newview/scripts/lua/leap.lua +++ b/indra/newview/scripts/lua/leap.lua @@ -40,8 +40,8 @@ local fiber = require('fiber') local ErrorQueue = require('ErrorQueue') --- local debug = require('printf') -local function debug(...) end +-- local dbg = require('printf') +local function dbg(...) end local leap = {} @@ -102,7 +102,7 @@ end -- -- See also request(), generate(). function leap.send(pump, data, reqid) - debug('leap.send(%s, %s, %s) entry', pump, data, reqid) + dbg('leap.send(%s, %s, %s) entry', pump, data, reqid) local data = data if type(data) == 'table' then data = table.clone(data) @@ -111,7 +111,7 @@ function leap.send(pump, data, reqid) data['reqid'] = reqid end end - debug('leap.send(%s, %s) calling post_on()', pump, data) + dbg('leap.send(%s, %s) calling post_on()', pump, data) LL.post_on(pump, data) end @@ -126,7 +126,7 @@ local function requestSetup(pump, data) -- WaitForReqid object in leap._pending so dispatch() can find it. leap._pending[reqid] = leap.WaitForReqid:new(reqid) -- Pass reqid to send() to stamp it into (a copy of) the request data. - debug('requestSetup(%s, %s)', pump, data) + dbg('requestSetup(%s, %s)', pump, data) leap.send(pump, data, reqid) return reqid end @@ -152,9 +152,9 @@ end function leap.request(pump, data) local reqid = requestSetup(pump, data) local waitfor = leap._pending[reqid] - debug('leap.request(%s, %s) about to wait on %s', pump, data, tostring(waitfor)) + dbg('leap.request(%s, %s) about to wait on %s', pump, data, tostring(waitfor)) local ok, response = pcall(waitfor.wait, waitfor) - debug('leap.request(%s, %s) got %s: %s', pump, data, ok, response) + dbg('leap.request(%s, %s) got %s: %s', pump, data, ok, response) -- kill off temporary WaitForReqid object, even if error leap._pending[reqid] = nil if ok then @@ -210,7 +210,7 @@ local function unsolicited(pump, data) -- we maintain waitfors in descending priority order, so the first waitfor -- to claim this event is the one with the highest priority for i, waitfor in pairs(leap._waitfors) do - debug('unsolicited() checking %s', waitfor.name) + dbg('unsolicited() checking %s', waitfor.name) if waitfor:handle(pump, data) then return end @@ -243,9 +243,9 @@ fiber.set_idle(function () cleanup('done') return 'done' end - debug('leap.idle() calling get_event_next()') + dbg('leap.idle() calling get_event_next()') local ok, pump, data = pcall(LL.get_event_next) - debug('leap.idle() got %s: %s, %s', ok, pump, data) + dbg('leap.idle() got %s: %s, %s', ok, pump, data) -- ok false means get_event_next() raised a Lua error, pump is message if not ok then cleanup(pump) @@ -368,9 +368,9 @@ end -- Block the calling coroutine until a suitable unsolicited event (one -- for which filter() returns the event) arrives. function leap.WaitFor:wait() - debug('%s about to wait', self.name) + dbg('%s about to wait', self.name) local item = self._queue:Dequeue() - debug('%s got %s', self.name, item) + dbg('%s got %s', self.name, item) return item end @@ -392,7 +392,7 @@ end -- called by unsolicited() for each WaitFor in leap._waitfors function leap.WaitFor:handle(pump, data) local item = self:filter(pump, data) - debug('%s.filter() returned %s', self.name, item) + dbg('%s.filter() returned %s', self.name, item) -- if this item doesn't pass the filter, we're not interested if not item then return false -- cgit v1.2.3 From bfeedacf5a32fb77bd505c43126f3b5dc4394296 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Wed, 27 Mar 2024 23:11:24 +0200 Subject: Run each script file with new LuaState --- indra/newview/llfloaterluadebug.cpp | 3 +- indra/newview/llluamanager.cpp | 39 +++++++------------ indra/newview/llluamanager.h | 8 ++-- indra/newview/scripts/lua/test_LLFloaterAbout.lua | 10 +---- indra/newview/scripts/lua/test_LLGesture.lua | 46 ++++++++++------------- 5 files changed, 39 insertions(+), 67 deletions(-) (limited to 'indra') diff --git a/indra/newview/llfloaterluadebug.cpp b/indra/newview/llfloaterluadebug.cpp index b63600e0e6..f247528231 100644 --- a/indra/newview/llfloaterluadebug.cpp +++ b/indra/newview/llfloaterluadebug.cpp @@ -111,8 +111,7 @@ void LLFloaterLUADebug::runSelectedScript(const std::vector &filena if (!filepath.empty()) { mScriptPath->setText(filepath); - cleanLuaState(); - LLLUAmanager::runScriptFile(mState, filepath, [this](int count, const LLSD& result) + LLLUAmanager::runScriptFile(filepath, [this](int count, const LLSD &result) { completion(count, result); }); diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index c65f062fd7..3194182ceb 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -195,61 +195,50 @@ lua_function(get_event_next, return 2; } -void LLLUAmanager::runScriptFile(const std::string& filename, 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); - runScriptFile(L, filename); -} - -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); -} - LLCoros::Future> -LLLUAmanager::startScriptFile(LuaState& L, const std::string& filename) +LLLUAmanager::startScriptFile(const std::string& filename) { // Despite returning from startScriptFile(), we need this Promise to // remain alive until the callback has fired. auto promise{ std::make_shared>>() }; - runScriptFile(L, filename, + runScriptFile(filename, [promise](int count, LLSD result) { promise->set_value({ count, result }); }); return LLCoros::getFuture(*promise); } -std::pair LLLUAmanager::waitScriptFile(LuaState& L, const std::string& filename) +std::pair LLLUAmanager::waitScriptFile(const std::string& filename) { - return startScriptFile(L, filename).get(); + return startScriptFile(filename).get(); } -void LLLUAmanager::runScriptFile(LuaState& L, const std::string& filename, script_result_fn cb) +void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn result_cb, script_finished_fn finished_cb) { - LLCoros::instance().launch(filename, [&L, filename, cb]() + // A script_result_fn will be called when LuaState::expr() completes. + LLCoros::instance().launch(filename, [filename, result_cb, finished_cb]() { llifstream in_file; in_file.open(filename.c_str()); if (in_file.is_open()) { + // A script_finished_fn is used to initialize the LuaState. + // It will be called when the LuaState is destroyed. + LuaState L(finished_cb); std::string text{std::istreambuf_iterator(in_file), {}}; auto [count, result] = L.expr(filename, text); - if (cb) + if (result_cb) { - cb(count, result); + result_cb(count, result); } } else { auto msg{ stringize("unable to open script file '", filename, "'") }; LL_WARNS("Lua") << msg << LL_ENDL; - if (cb) + if (result_cb) { - cb(-1, msg); + result_cb(-1, msg); } } }); diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h index fb9f1b8141..a297d14502 100644 --- a/indra/newview/llluamanager.h +++ b/indra/newview/llluamanager.h @@ -53,19 +53,17 @@ public: // results, represented as the entries of the result array. typedef std::function 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 = {}); + static void runScriptFile(const std::string &filename, script_result_fn result_cb = {}, script_finished_fn finished_cb = {}); // Start running a Lua script file, returning an LLCoros::Future whose // get() method will pause the calling coroutine until it can deliver the // (count, result) pair described above. Between startScriptFile() and // Future::get(), the caller and the Lua script coroutine will run // concurrently. static LLCoros::Future> - startScriptFile(LuaState& L, const std::string& filename); + startScriptFile(const std::string& filename); // 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 waitScriptFile(LuaState& L, const std::string& filename); + static std::pair waitScriptFile(const std::string& filename); static void runScriptLine(const std::string &chunk, script_finished_fn cb = {}); static void runScriptLine(const std::string &chunk, script_result_fn cb); diff --git a/indra/newview/scripts/lua/test_LLFloaterAbout.lua b/indra/newview/scripts/lua/test_LLFloaterAbout.lua index 7abc437b79..6bbf61982d 100644 --- a/indra/newview/scripts/lua/test_LLFloaterAbout.lua +++ b/indra/newview/scripts/lua/test_LLFloaterAbout.lua @@ -1,14 +1,6 @@ -- test LLFloaterAbout LLFloaterAbout = require('LLFloaterAbout') -leap = require('leap') -coro = require('coro') inspect = require('inspect') -coro.launch(function () - print(inspect(LLFloaterAbout.getInfo())) - leap.done() -end) - -leap.process() - +print(inspect(LLFloaterAbout.getInfo())) diff --git a/indra/newview/scripts/lua/test_LLGesture.lua b/indra/newview/scripts/lua/test_LLGesture.lua index 5897a0e3cb..1cce674565 100644 --- a/indra/newview/scripts/lua/test_LLGesture.lua +++ b/indra/newview/scripts/lua/test_LLGesture.lua @@ -2,31 +2,25 @@ LLGesture = require 'LLGesture' inspect = require 'inspect' -coro = require 'coro' -leap = require 'leap' -coro.launch(function() - -- getActiveGestures() returns {: {name, playing, trigger}} - gestures_uuid = LLGesture.getActiveGestures() - -- convert to {: } - gestures = {} - for uuid, info in pairs(gestures_uuid) do - gestures[info.name] = uuid - end - -- now run through the list - for name, uuid in pairs(gestures) do - if name == 'afk' then - -- afk has a long timeout, and isn't interesting to look at - continue - end - print(name) - LLGesture.startGesture(uuid) - repeat - LL.sleep(1) - until not LLGesture.isGesturePlaying(uuid) - end - print('Done.') - leap.done() -end) -leap.process() +-- getActiveGestures() returns {: {name, playing, trigger}} +gestures_uuid = LLGesture.getActiveGestures() +-- convert to {: } +gestures = {} +for uuid, info in pairs(gestures_uuid) do + gestures[info.name] = uuid +end +-- now run through the list +for name, uuid in pairs(gestures) do + if name == 'afk' then + -- afk has a long timeout, and isn't interesting to look at + continue + end + print(name) + LLGesture.startGesture(uuid) + repeat + LL.sleep(1) + until not LLGesture.isGesturePlaying(uuid) +end +print('Done.') -- cgit v1.2.3 From db6732401af13c3e283c7a0a33fc9c379a8798a6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 28 Mar 2024 07:06:54 -0400 Subject: Move our lua_register(), lua_rawlen() from lua_function.h to .cpp. --- indra/llcommon/lua_function.cpp | 3 +++ indra/llcommon/lua_function.h | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 9f0abd5674..e731408c7d 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -31,6 +31,9 @@ #include "lualistener.h" #include "stringize.h" +#define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n))) +#define lua_rawlen lua_objlen + /***************************************************************************** * luau namespace *****************************************************************************/ diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 07848e38af..868c13c3f1 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -23,9 +23,6 @@ class LuaListener; -#define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n))) -#define lua_rawlen lua_objlen - namespace lluau { // luau defines luaL_error() as void, but we want to use the Lua idiom of -- cgit v1.2.3 From 10af340aa35b3dcdc0c00a03743d33f33e627ad7 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 28 Mar 2024 07:08:36 -0400 Subject: Clean up unused llevents.h #includes. --- indra/llcommon/llevents.h | 14 -------------- 1 file changed, 14 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 77a405871d..c1b752a143 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -48,27 +48,13 @@ #pragma warning (pop) #endif -#include -#include // noncopyable #include -#include -#include // reference_wrapper -#include -#include #include "llsd.h" #include "llsingleton.h" #include "lldependencies.h" -#include "llstl.h" #include "llexception.h" #include "llhandle.h" -/*==========================================================================*| -// override this to allow binding free functions with more parameters -#ifndef LLEVENTS_LISTENER_ARITY -#define LLEVENTS_LISTENER_ARITY 10 -#endif -|*==========================================================================*/ - // hack for testing #ifndef testable #define testable private -- cgit v1.2.3 From 9fbfd3d0ad1ed2cb721129a59421f06fedd071bf Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 28 Mar 2024 07:10:10 -0400 Subject: Remove llluamanager.cpp "FIXME extremely hacky way" cruft. --- indra/newview/llluamanager.cpp | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'indra') diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index c65f062fd7..d1fcf21941 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -35,16 +35,6 @@ #include "lualistener.h" #include "stringize.h" -// skip all these link dependencies for integration testing -#ifndef LL_TEST -#include "lluilistener.h" -#include "llviewercontrol.h" - -// FIXME extremely hacky way to get to the UI Listener framework. There's -// a cleaner way. -extern LLUIListener sUIListener; -#endif // ! LL_TEST - #include #include -- cgit v1.2.3 From ce73e5c5ab0c33673067d9322c98ae8800fa9224 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 28 Mar 2024 07:11:31 -0400 Subject: Terminate Lua scripts hanging in LL.get_event_next(). Make LuaListener listen for "LLApp" viewer shutdown events. On receiving such, it closes its queue. Then the C++ coroutine calling getNext() wakes up with an LLThreadSafeQueue exception, and calls LLCoros::checkStop() to throw one of the exceptions recognized by LLCoros::toplevel(). Add an llluamanager_test.cpp test to verify this behavior. --- indra/llcommon/lualistener.cpp | 31 +++++++++++++++++++++++++++++-- indra/llcommon/lualistener.h | 2 ++ indra/newview/tests/llluamanager_test.cpp | 21 +++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lualistener.cpp b/indra/llcommon/lualistener.cpp index ed34133924..37ce27a2a4 100644 --- a/indra/llcommon/lualistener.cpp +++ b/indra/llcommon/lualistener.cpp @@ -36,7 +36,24 @@ LuaListener::LuaListener(lua_State* L): mListener(new LLLeapListener( "LuaListener", [this](const std::string& pump, const LLSD& data) - { return queueEvent(pump, data); })) + { return queueEvent(pump, data); })), + // Listen for shutdown events on the "LLApp" LLEventPump. + mShutdownConnection( + LLEventPumps::instance().obtain("LLApp").listen( + LLEventPump::inventName("LuaState"), + [this](const LLSD& status) + { + LL_DEBUGS("LuaListener") << "caught " << status << LL_ENDL; + const auto& statsd = status["status"]; + if (statsd.asString() != "running") + { + // If a Lua script is still blocked in getNext() during + // viewer shutdown, close the queue to wake up getNext(). + LL_DEBUGS("LuaListener") << "closing queue" << LL_ENDL; + mQueue.close(); + } + return false; + })) {} LuaListener::~LuaListener() @@ -87,5 +104,15 @@ bool LuaListener::queueEvent(const std::string& pump, const LLSD& data) LuaListener::PumpData LuaListener::getNext() { - return mQueue.pop(); + try + { + return mQueue.pop(); + } + catch (const LLThreadSafeQueueInterrupt& exc) + { + // mQueue has been closed. The only way that happens is when we detect + // viewer shutdown. Terminate the calling coroutine. + LLCoros::checkStop(); + return {}; + } } diff --git a/indra/llcommon/lualistener.h b/indra/llcommon/lualistener.h index c13b7bbd5f..40ccfba8fe 100644 --- a/indra/llcommon/lualistener.h +++ b/indra/llcommon/lualistener.h @@ -12,6 +12,7 @@ #if ! defined(LL_LUALISTENER_H) #define LL_LUALISTENER_H +#include "llevents.h" #include "llinstancetracker.h" #include "llsd.h" #include "llthreadsafequeue.h" @@ -73,6 +74,7 @@ private: LLThreadSafeQueue mQueue; std::unique_ptr mListener; + LLTempBoundListener mShutdownConnection; }; #endif /* ! defined(LL_LUALISTENER_H) */ diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index e10dedcf53..682289bd93 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -418,4 +418,25 @@ namespace tut auto [count, result] = future.get(); ensure_equals("leap.lua: " + result.asString(), count, 0); } + + template<> template<> + void object::test<7>() + { + set_test_name("stop hanging Lua script"); + const std::string lua( + "-- hanging Lua script should terminate\n" + "\n" + "LL.get_event_next()\n" + ); + LuaState L; + auto future = LLLUAmanager::startScriptLine(L, lua); + // The problem with this test is that the LuaState is destroyed + // (disconnecting the listener) before the LLTestApp instance mApp is + // destroyed (sending the shutdown event). Explicitly simulate LLApp's + // event. + LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", "quitting")); + // but now we have to give the startScriptLine() coroutine a chance to run + auto [count, result] = future.get(); + ensure_equals(result.asString(), count, 0); + } } // namespace tut -- cgit v1.2.3 From fc6f8197f5e8764d0c2c0234437a7003f1ce6a0d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 28 Mar 2024 11:36:46 -0400 Subject: Eliminate unreferenced exception name --- indra/llcommon/lualistener.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/lualistener.cpp b/indra/llcommon/lualistener.cpp index 37ce27a2a4..ef9782824e 100644 --- a/indra/llcommon/lualistener.cpp +++ b/indra/llcommon/lualistener.cpp @@ -108,7 +108,7 @@ LuaListener::PumpData LuaListener::getNext() { return mQueue.pop(); } - catch (const LLThreadSafeQueueInterrupt& exc) + catch (const LLThreadSafeQueueInterrupt&) { // mQueue has been closed. The only way that happens is when we detect // viewer shutdown. Terminate the calling coroutine. -- cgit v1.2.3 From 15de45261585e8b2d89c5df9091639ecca1e20c4 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 28 Mar 2024 11:48:34 -0400 Subject: Ditch a couple LL_DEBUGS() messages. --- indra/llcommon/lualistener.cpp | 2 -- 1 file changed, 2 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lualistener.cpp b/indra/llcommon/lualistener.cpp index ef9782824e..018a31d5a8 100644 --- a/indra/llcommon/lualistener.cpp +++ b/indra/llcommon/lualistener.cpp @@ -43,13 +43,11 @@ LuaListener::LuaListener(lua_State* L): LLEventPump::inventName("LuaState"), [this](const LLSD& status) { - LL_DEBUGS("LuaListener") << "caught " << status << LL_ENDL; const auto& statsd = status["status"]; if (statsd.asString() != "running") { // If a Lua script is still blocked in getNext() during // viewer shutdown, close the queue to wake up getNext(). - LL_DEBUGS("LuaListener") << "closing queue" << LL_ENDL; mQueue.close(); } return false; -- cgit v1.2.3 From ba74839cd19c3b17757345ba81b1aa97c57f43d4 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 28 Mar 2024 11:49:19 -0400 Subject: Use LLApp::setQuitting(). Expect killed-script error. --- indra/newview/tests/llluamanager_test.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'indra') diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index 682289bd93..b907ac6619 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -430,13 +430,12 @@ namespace tut ); LuaState L; auto future = LLLUAmanager::startScriptLine(L, lua); - // The problem with this test is that the LuaState is destroyed - // (disconnecting the listener) before the LLTestApp instance mApp is - // destroyed (sending the shutdown event). Explicitly simulate LLApp's - // event. - LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", "quitting")); + // Poke LLTestApp to send its preliminary shutdown message. + mApp.setQuitting(); // but now we have to give the startScriptLine() coroutine a chance to run auto [count, result] = future.get(); - ensure_equals(result.asString(), count, 0); + ensure_equals("killed Lua script terminated normally", count, -1); + ensure_equals("unexpected killed Lua script error", + result.asString(), "viewer is stopping"); } } // namespace tut -- cgit v1.2.3 From 53ce38b106a086a4e3bc1ed0663bb47b0f0d967c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 28 Mar 2024 12:11:34 -0400 Subject: Remove rest of prototype UI access. --- indra/newview/llluamanager.cpp | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) (limited to 'indra') diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index d1fcf21941..74306a60ba 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -31,6 +31,7 @@ #include "llcoros.h" #include "llerror.h" #include "lleventcoro.h" +#include "llviewercontrol.h" #include "lua_function.h" #include "lualistener.h" #include "stringize.h" @@ -116,37 +117,6 @@ lua_function(print_warning, "print_warning(args...): WARNING level logging") return 0; } -#ifndef LL_TEST - -lua_function(run_ui_command, - "run_ui_command(name [, parameter]): " - "call specified UI command with specified parameter") -{ - int top = lua_gettop(L); - std::string func_name; - if (top >= 1) - { - func_name = lua_tostring(L,1); - } - std::string parameter; - if (top >= 2) - { - parameter = lua_tostring(L,2); - } - LL_WARNS("LUA") << "running ui func " << func_name << " parameter " << parameter << LL_ENDL; - LLSD event; - event["function"] = func_name; - if (!parameter.empty()) - { - event["parameter"] = parameter; - } - sUIListener.call(event); - - lua_settop(L, 0); - return 0; -} -#endif // ! LL_TEST - lua_function(post_on, "post_on(pumpname, data): post specified data to specified LLEventPump") { std::string pumpname{ lua_tostdstring(L, 1) }; -- cgit v1.2.3 From 836f8dc26f2041aea51c5c953b99f34859db6387 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Mon, 1 Apr 2024 13:13:53 +0300 Subject: Add 'Lua Scripts' floater --- indra/llwindow/llwindow.h | 2 + indra/llwindow/llwindowmacosx.h | 2 + indra/llwindow/llwindowwin32.cpp | 39 ++++--- indra/llwindow/llwindowwin32.h | 2 + indra/newview/CMakeLists.txt | 2 + indra/newview/llfloaterluascripts.cpp | 123 +++++++++++++++++++++ indra/newview/llfloaterluascripts.h | 55 +++++++++ indra/newview/llluamanager.cpp | 5 + indra/newview/llluamanager.h | 5 + indra/newview/llviewerfloaterreg.cpp | 2 + .../skins/default/xui/en/floater_lua_scripts.xml | 36 ++++++ .../skins/default/xui/en/menu_lua_scripts.xml | 11 ++ indra/newview/skins/default/xui/en/menu_viewer.xml | 10 ++ 13 files changed, 279 insertions(+), 15 deletions(-) create mode 100644 indra/newview/llfloaterluascripts.cpp create mode 100644 indra/newview/llfloaterluascripts.h create mode 100644 indra/newview/skins/default/xui/en/floater_lua_scripts.xml create mode 100644 indra/newview/skins/default/xui/en/menu_lua_scripts.xml (limited to 'indra') diff --git a/indra/llwindow/llwindow.h b/indra/llwindow/llwindow.h index f435d46584..9e9e424455 100644 --- a/indra/llwindow/llwindow.h +++ b/indra/llwindow/llwindow.h @@ -187,6 +187,8 @@ public: virtual void interruptLanguageTextInput() {} virtual void spawnWebBrowser(const std::string& escaped_url, bool async) {}; + virtual void openFolder(const std::string &path) {}; + static std::vector getDynamicFallbackFontList(); // Provide native key event data diff --git a/indra/llwindow/llwindowmacosx.h b/indra/llwindow/llwindowmacosx.h index 7614167213..5f728fb72e 100644 --- a/indra/llwindow/llwindowmacosx.h +++ b/indra/llwindow/llwindowmacosx.h @@ -116,6 +116,8 @@ public: void spawnWebBrowser(const std::string& escaped_url, bool async) override; F32 getSystemUISize() override; + void openFolder(const std::string &path) override; + static std::vector getDisplaysResolutionList(); static std::vector getDynamicFallbackFontList(); diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index 057d7a700e..e3ef28a27d 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -3755,6 +3755,25 @@ S32 OSMessageBoxWin32(const std::string& text, const std::string& caption, U32 t return retval; } +void shell_open(const std::string &file, bool async) +{ + // this is madness.. no, this is.. + LLWString url_wstring = utf8str_to_wstring(file); + llutf16string url_utf16 = wstring_to_utf16str(url_wstring); + + // let the OS decide what to use to open the URL + SHELLEXECUTEINFO sei = {sizeof(sei)}; + // NOTE: this assumes that SL will stick around long enough to complete the DDE message exchange + // necessary for ShellExecuteEx to complete + if (async) + { + sei.fMask = SEE_MASK_ASYNCOK; + } + sei.nShow = SW_SHOWNORMAL; + sei.lpVerb = L"open"; + sei.lpFile = url_utf16.c_str(); + ShellExecuteEx(&sei); +} void LLWindowWin32::spawnWebBrowser(const std::string& escaped_url, bool async) { @@ -3780,22 +3799,12 @@ void LLWindowWin32::spawnWebBrowser(const std::string& escaped_url, bool async) // replaced ShellExecute code with ShellExecuteEx since ShellExecute doesn't work // reliablly on Vista. - // this is madness.. no, this is.. - LLWString url_wstring = utf8str_to_wstring( escaped_url ); - llutf16string url_utf16 = wstring_to_utf16str( url_wstring ); + shell_open(escaped_url, async); +} - // let the OS decide what to use to open the URL - SHELLEXECUTEINFO sei = { sizeof( sei ) }; - // NOTE: this assumes that SL will stick around long enough to complete the DDE message exchange - // necessary for ShellExecuteEx to complete - if (async) - { - sei.fMask = SEE_MASK_ASYNCOK; - } - sei.nShow = SW_SHOWNORMAL; - sei.lpVerb = L"open"; - sei.lpFile = url_utf16.c_str(); - ShellExecuteEx( &sei ); +void LLWindowWin32::openFolder(const std::string &path) +{ + shell_open(path, false); } /* diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h index ff287a140e..ed64891108 100644 --- a/indra/llwindow/llwindowwin32.h +++ b/indra/llwindow/llwindowwin32.h @@ -122,6 +122,8 @@ public: /*virtual*/ void interruptLanguageTextInput(); /*virtual*/ void spawnWebBrowser(const std::string& escaped_url, bool async); + void openFolder(const std::string &path); + /*virtual*/ F32 getSystemUISize(); LLWindowCallbacks::DragNDropResult completeDragNDropRequest( const LLCoordGL gl_coord, const MASK mask, LLWindowCallbacks::DragNDropAction action, const std::string url ); diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index b26ce7d06d..f41e481b26 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -243,6 +243,7 @@ set(viewer_SOURCE_FILES llfloaterlinkreplace.cpp llfloaterloadprefpreset.cpp llfloaterluadebug.cpp + llfloaterluascripts.cpp llfloatermarketplacelistings.cpp llfloatermap.cpp llfloatermediasettings.cpp @@ -901,6 +902,7 @@ set(viewer_HEADER_FILES llfloaterlinkreplace.h llfloaterloadprefpreset.h llfloaterluadebug.h + llfloaterluascripts.h llfloatermap.h llfloatermarketplacelistings.h llfloatermediasettings.h diff --git a/indra/newview/llfloaterluascripts.cpp b/indra/newview/llfloaterluascripts.cpp new file mode 100644 index 0000000000..87d2cf0c69 --- /dev/null +++ b/indra/newview/llfloaterluascripts.cpp @@ -0,0 +1,123 @@ +/** + * @file llfloaterluascriptsinfo.cpp + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterluascripts.h" +#include "llevents.h" +#include +#include "llluamanager.h" +#include "llscrolllistctrl.h" +#include "llviewerwindow.h" +#include "llwindow.h" +#include "llviewermenu.h" + +const F32 REFRESH_INTERVAL = 1.0f; + +LLFloaterLUAScripts::LLFloaterLUAScripts(const LLSD &key) + : LLFloater(key), + mUpdateTimer(new LLTimer()), + mContextMenuHandle() +{ + mCommitCallbackRegistrar.add("Script.OpenFolder", [this](LLUICtrl*, const LLSD &userdata) + { + gViewerWindow->getWindow()->openFolder(mTargetFolderPath); + }); +} + + +BOOL LLFloaterLUAScripts::postBuild() +{ + mScriptList = getChild("scripts_list"); + mScriptList->setRightMouseDownCallback(boost::bind(&LLFloaterLUAScripts::onScrollListRightClicked, this, _1, _2, _3)); + + LLContextMenu *menu = LLUICtrlFactory::getInstance()->createFromFile( + "menu_lua_scripts.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if (menu) + { + mContextMenuHandle = menu->getHandle(); + } + + return TRUE; +} + +LLFloaterLUAScripts::~LLFloaterLUAScripts() +{ + auto menu = mContextMenuHandle.get(); + if (menu) + { + menu->die(); + mContextMenuHandle.markDead(); + } + + delete mUpdateTimer; +} + +void LLFloaterLUAScripts::draw() +{ + if (mUpdateTimer->hasExpired()) + { + populateScriptList(); + } + LLFloater::draw(); +} + +void LLFloaterLUAScripts::populateScriptList() +{ + S32 prev_pos = mScriptList->getScrollPos(); + LLSD prev_selected = mScriptList->getSelectedValue(); + mScriptList->clearRows(); + mScriptList->updateColumns(true); + std::map scripts = LLLUAmanager::getScriptNames(); + for (auto &it : scripts) + { + LLSD row; + row["value"] = it.first; + row["columns"][0]["value"] = std::filesystem::path((it.second)).stem().string(); + row["columns"][0]["column"] = "script_name"; + row["columns"][1]["value"] = it.second; + row["columns"][1]["column"] = "script_path"; + mScriptList->addElement(row); + } + mScriptList->setScrollPos(prev_pos); + mScriptList->setSelectedByValue(prev_selected, true); + mUpdateTimer->setTimerExpirySec(REFRESH_INTERVAL); +} + +void LLFloaterLUAScripts::onScrollListRightClicked(LLUICtrl *ctrl, S32 x, S32 y) +{ + LLScrollListItem *item = mScriptList->hitItem(x, y); + if (item) + { + mScriptList->selectItemAt(x, y, MASK_NONE); + auto menu = mContextMenuHandle.get(); + if (menu) + { + mTargetFolderPath = std::filesystem::path((item->getColumn(1)->getValue().asString())).parent_path().string(); + menu->show(x, y); + LLMenuGL::showPopup(this, menu, x, y); + } + } +} diff --git a/indra/newview/llfloaterluascripts.h b/indra/newview/llfloaterluascripts.h new file mode 100644 index 0000000000..c8c9e7f020 --- /dev/null +++ b/indra/newview/llfloaterluascripts.h @@ -0,0 +1,55 @@ +/** + * @file llfloaterluascriptsinfo.h + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERLUASCRIPTS_H +#define LL_LLFLOATERLUASCRIPTS_H + +#include "llfloater.h" + +class LLScrollListCtrl; + +class LLFloaterLUAScripts : + public LLFloater +{ + public: + LLFloaterLUAScripts(const LLSD &key); + virtual ~LLFloaterLUAScripts(); + + BOOL postBuild(); + void draw(); + +private: + void populateScriptList(); + void onScrollListRightClicked(LLUICtrl *ctrl, S32 x, S32 y); + + LLTimer* mUpdateTimer; + LLScrollListCtrl* mScriptList; + std::string mTargetFolderPath; + + LLHandle mContextMenuHandle; +}; + +#endif // LL_LLFLOATERLUASCRIPTS_H + diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index be332a7244..343c7c7459 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -48,6 +48,8 @@ #include #include +std::map LLLUAmanager::sScriptNames; + lua_function(sleep, "sleep(seconds): pause the running coroutine") { F32 seconds = lua_tonumber(L, -1); @@ -177,6 +179,8 @@ void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn r // A script_result_fn will be called when LuaState::expr() completes. LLCoros::instance().launch(filename, [filename, result_cb, finished_cb]() { + std::string coro_name = LLCoros::getName(); + sScriptNames[coro_name] = filename; llifstream in_file; in_file.open(filename.c_str()); @@ -201,6 +205,7 @@ void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn r result_cb(-1, msg); } } + sScriptNames.erase(coro_name); }); } diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h index a297d14502..70728f958e 100644 --- a/indra/newview/llluamanager.h +++ b/indra/newview/llluamanager.h @@ -80,6 +80,11 @@ public: static std::pair waitScriptLine(LuaState& L, const std::string& chunk); static void runScriptOnLogin(); + + static std::map getScriptNames() { return sScriptNames; } + + private: + static std::map sScriptNames; }; class LLRequireResolver diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index f72ef71241..de328639f8 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -94,6 +94,7 @@ #include "llfloaterlinkreplace.h" #include "llfloaterloadprefpreset.h" #include "llfloaterluadebug.h" +#include "llfloaterluascripts.h" #include "llfloatermap.h" #include "llfloatermarketplacelistings.h" #include "llfloatermediasettings.h" @@ -402,6 +403,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("load_pref_preset", "floater_load_pref_preset.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("lua_debug", "floater_lua_debug.xml", (LLFloaterBuildFunc) &LLFloaterReg::build); + LLFloaterReg::add("lua_scripts", "floater_lua_scripts.xml", (LLFloaterBuildFunc) &LLFloaterReg::build); LLFloaterReg::add("mem_leaking", "floater_mem_leaking.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); diff --git a/indra/newview/skins/default/xui/en/floater_lua_scripts.xml b/indra/newview/skins/default/xui/en/floater_lua_scripts.xml new file mode 100644 index 0000000000..6859201650 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_lua_scripts.xml @@ -0,0 +1,36 @@ + + + + + + + diff --git a/indra/newview/skins/default/xui/en/menu_lua_scripts.xml b/indra/newview/skins/default/xui/en/menu_lua_scripts.xml new file mode 100644 index 0000000000..8f718abe17 --- /dev/null +++ b/indra/newview/skins/default/xui/en/menu_lua_scripts.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index b259b101b4..40f5c40dfe 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -2539,6 +2539,16 @@ function="World.EnvPreset" function="Floater.Toggle" parameter="lua_debug" /> + + + + Date: Mon, 1 Apr 2024 15:33:15 +0300 Subject: open folder support for mac --- indra/llwindow/llwindowmacosx-objc.h | 2 ++ indra/llwindow/llwindowmacosx-objc.mm | 7 +++++++ indra/llwindow/llwindowmacosx.cpp | 5 +++++ 3 files changed, 14 insertions(+) (limited to 'indra') diff --git a/indra/llwindow/llwindowmacosx-objc.h b/indra/llwindow/llwindowmacosx-objc.h index 77024d3a9c..ade303c6d4 100644 --- a/indra/llwindow/llwindowmacosx-objc.h +++ b/indra/llwindow/llwindowmacosx-objc.h @@ -177,6 +177,8 @@ void setMarkedText(unsigned short *text, unsigned int *selectedRange, unsigned i void getPreeditLocation(float *location, unsigned int length); void allowDirectMarkedTextInput(bool allow, GLViewRef glView); +void openFolderWithFinder(const char *folder_path); + NSWindowRef getMainAppWindow(); GLViewRef getGLView(); diff --git a/indra/llwindow/llwindowmacosx-objc.mm b/indra/llwindow/llwindowmacosx-objc.mm index 690fe058db..56d1798dcf 100644 --- a/indra/llwindow/llwindowmacosx-objc.mm +++ b/indra/llwindow/llwindowmacosx-objc.mm @@ -462,6 +462,13 @@ long showAlert(std::string text, std::string title, int type) return ret; } +void openFolderWithFinder(const char *folder_path) +{ + @autoreleasepool { + NSString *folderPathString = [NSString stringWithUTF8String:folder_path]; + [[NSWorkspace sharedWorkspace] openFile:folderPathString withApplication:@"Finder"]; + } +} /* GLViewRef getGLView() { diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp index 778e5d3898..b774597eb6 100644 --- a/indra/llwindow/llwindowmacosx.cpp +++ b/indra/llwindow/llwindowmacosx.cpp @@ -2076,6 +2076,11 @@ F32 LLWindowMacOSX::getSystemUISize() return gHiDPISupport ? ::getDeviceUnitSize(mGLView) : LLWindow::getSystemUISize(); } +void LLWindowMacOSX::openFolder(const std::string &path) +{ + openFolderWithFinder(path.c_str()); +} + #if LL_OS_DRAGDROP_ENABLED /* S16 LLWindowMacOSX::dragTrackingHandler(DragTrackingMessage message, WindowRef theWindow, -- cgit v1.2.3 From f45ae0def221b4e1911e83f2da528174cf1d8a0d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 2 Apr 2024 11:09:03 -0400 Subject: Defend leap.request(), generate() from garbage collection. Earlier we had blithely designated the 'pending' list (which stores WaitForReqid objects for pending request() and generate() calls) as a weak table. But the caller of request() or generate() does not hold a reference to the WaitForReqid object. Make pending hold "strong" references. Private collections (pending, waitfors) and private scalars that are never reassigned (reply, command) need not be entries in the leap table. --- indra/newview/scripts/lua/leap.lua | 77 ++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 37 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/leap.lua b/indra/newview/scripts/lua/leap.lua index 9da1839c68..d19273e8bc 100644 --- a/indra/newview/scripts/lua/leap.lua +++ b/indra/newview/scripts/lua/leap.lua @@ -40,25 +40,25 @@ local fiber = require('fiber') local ErrorQueue = require('ErrorQueue') --- local dbg = require('printf') local function dbg(...) end +-- local dbg = require('printf') local leap = {} --- _reply: string name of reply LLEventPump. Any events the viewer posts to +-- reply: string name of reply LLEventPump. Any events the viewer posts to -- this pump will be queued for get_event_next(). We usually specify it as the -- reply pump for requests to internal viewer services. --- _command: string name of command LLEventPump. post_to(_command, ...) +-- command: string name of command LLEventPump. post_to(command, ...) -- engages LLLeapListener operations such as listening on a specified other -- LLEventPump, etc. -leap._reply, leap._command = LL.get_event_pumps() +local reply, command = LL.get_event_pumps() -- Dict of features added to the LEAP protocol since baseline implementation. -- Before engaging a new feature that might break an older viewer, we can -- check for the presence of that feature key. This table is solely about the -- LEAP protocol itself, the way we communicate with the viewer. To discover -- whether a given listener exists, or supports a particular operation, use --- _command's "getAPI" operation. --- For Lua, _command's "getFeatures" operation suffices? +-- command's "getAPI" operation. +-- For Lua, command's "getFeatures" operation suffices? -- leap._features = {} -- Each outstanding request() or generate() call has a corresponding @@ -67,20 +67,25 @@ leap._reply, leap._command = LL.get_event_pumps() -- we can look up the appropriate WaitForReqid object more efficiently -- in a dict than by tossing such objects into the usual waitfors list. -- Note: the ["reqid"] must be unique, otherwise we could end up --- replacing an earlier WaitForReqid object in self.pending with a +-- replacing an earlier WaitForReqid object in pending with a -- later one. That means that no incoming event will ever be given to -- the old WaitForReqid object. Any coroutine waiting on the discarded -- WaitForReqid object would therefore wait forever. --- these are weak values tables -local weak_values = {__mode='v'} -leap._pending = setmetatable({}, weak_values) +-- pending is NOT a weak table because the caller of request() or generate() +-- never sees the WaitForReqid object. pending holds the only reference, so +-- it should NOT be garbage-collected. +pending = {} -- Our consumer will instantiate some number of WaitFor subclass objects. -- As these are traversed in descending priority order, we must keep -- them in a list. -leap._waitfors = setmetatable({}, weak_values) +-- Anyone who instantiates a WaitFor subclass object should retain a reference +-- to it. Once the consuming script drops the reference, allow Lua to +-- garbage-collect the WaitFor despite its entry in waitfors. +local weak_values = {__mode='v'} +waitfors = setmetatable({}, weak_values) -- It has been suggested that we should use UUIDs as ["reqid"] values, -- since UUIDs are guaranteed unique. However, as the "namespace" for --- ["reqid"] values is our very own _reply pump, we can get away with +-- ["reqid"] values is our very own reply pump, we can get away with -- an integer. leap._reqid = 0 -- break leap.process() loop @@ -88,12 +93,12 @@ leap._done = false -- get the name of the reply pump function leap.replypump() - return leap._reply + return reply end -- get the name of the command pump function leap.cmdpump() - return leap._command + return command end -- Fire and forget. Send the specified request LLSD, expecting no reply. @@ -102,11 +107,10 @@ end -- -- See also request(), generate(). function leap.send(pump, data, reqid) - dbg('leap.send(%s, %s, %s) entry', pump, data, reqid) local data = data if type(data) == 'table' then data = table.clone(data) - data['reply'] = leap._reply + data['reply'] = reply if reqid ~= nil then data['reqid'] = reqid end @@ -122,13 +126,14 @@ local function requestSetup(pump, data) local reqid = leap._reqid -- Instantiate a new WaitForReqid object. The priority is irrelevant -- because, unlike the WaitFor base class, WaitForReqid does not - -- self-register on our leap._waitfors list. Instead, capture the new - -- WaitForReqid object in leap._pending so dispatch() can find it. - leap._pending[reqid] = leap.WaitForReqid:new(reqid) + -- self-register on our waitfors list. Instead, capture the new + -- WaitForReqid object in pending so dispatch() can find it. + local waitfor = leap.WaitForReqid:new(reqid) + pending[reqid] = waitfor -- Pass reqid to send() to stamp it into (a copy of) the request data. dbg('requestSetup(%s, %s)', pump, data) leap.send(pump, data, reqid) - return reqid + return reqid, waitfor end -- Send the specified request LLSD, expecting exactly one reply. Block @@ -150,13 +155,12 @@ end -- -- See also send(), generate(). function leap.request(pump, data) - local reqid = requestSetup(pump, data) - local waitfor = leap._pending[reqid] + local reqid, waitfor = requestSetup(pump, data) dbg('leap.request(%s, %s) about to wait on %s', pump, data, tostring(waitfor)) local ok, response = pcall(waitfor.wait, waitfor) dbg('leap.request(%s, %s) got %s: %s', pump, data, ok, response) -- kill off temporary WaitForReqid object, even if error - leap._pending[reqid] = nil + pending[reqid] = nil if ok then return response else @@ -178,8 +182,7 @@ function leap.generate(pump, data, checklast) -- Invent a new, unique reqid. Arrange to handle incoming events -- bearing that reqid. Stamp the outbound request with that reqid, and -- send it. - local reqid = requestSetup(pump, data) - local waitfor = leap._pending[reqid] + local reqid, waitfor = requestSetup(pump, data) local ok, response repeat ok, response = pcall(waitfor.wait, waitfor) @@ -189,7 +192,7 @@ function leap.generate(pump, data, checklast) coroutine.yield(response) until checklast and checklast(response) -- If we break the above loop, whether or not due to error, clean up. - leap._pending[reqid] = nil + pending[reqid] = nil if not ok then error(response) end @@ -197,10 +200,10 @@ end local function cleanup(message) -- we're done: clean up all pending coroutines - for i, waitfor in pairs(leap._pending) do + for i, waitfor in pairs(pending) do waitfor:close() end - for i, waitfor in pairs(leap._waitfors) do + for i, waitfor in pairs(waitfors) do waitfor:close() end end @@ -209,7 +212,7 @@ end local function unsolicited(pump, data) -- we maintain waitfors in descending priority order, so the first waitfor -- to claim this event is the one with the highest priority - for i, waitfor in pairs(leap._waitfors) do + for i, waitfor in pairs(waitfors) do dbg('unsolicited() checking %s', waitfor.name) if waitfor:handle(pump, data) then return @@ -226,7 +229,7 @@ local function dispatch(pump, data) return unsolicited(pump, data) end -- have reqid; do we have a WaitForReqid? - local waitfor = leap._pending[reqid] + local waitfor = pending[reqid] if waitfor == nil then return unsolicited(pump, data) end @@ -268,17 +271,17 @@ end -- called by WaitFor.enable() local function registerWaitFor(waitfor) - table.insert(leap._waitfors, waitfor) + table.insert(waitfors, waitfor) -- keep waitfors sorted in descending order of specified priority - table.sort(leap._waitfors, + table.sort(waitfors, function (lhs, rhs) return lhs.priority > rhs.priority end) end -- called by WaitFor.disable() local function unregisterWaitFor(waitfor) - for i, w in pairs(leap._waitfors) do + for i, w in pairs(waitfors) do if w == waitfor then - leap._waitfors[i] = nil + waitfors[i] = nil break end end @@ -389,7 +392,7 @@ function leap.WaitFor:filter(pump, data) error('You must override the WaitFor.filter() method') end --- called by unsolicited() for each WaitFor in leap._waitfors +-- called by unsolicited() for each WaitFor in waitfors function leap.WaitFor:handle(pump, data) local item = self:filter(pump, data) dbg('%s.filter() returned %s', self.name, item) @@ -423,8 +426,8 @@ leap.WaitForReqid = leap.WaitFor:new() function leap.WaitForReqid:new(reqid) -- priority is meaningless, since this object won't be added to the - -- priority-sorted ViewerClient.waitfors list. Use the reqid as the - -- debugging name string. + -- priority-sorted waitfors list. Use the reqid as the debugging name + -- string. local obj = leap.WaitFor:new(nil, 'WaitForReqid(' .. reqid .. ')') setmetatable(obj, self) self.__index = self -- cgit v1.2.3 From 233de4561c25df24dd787079ed7d7d98cbc40ff2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 2 Apr 2024 11:13:37 -0400 Subject: Give LLLeapListener's "listen" operation a "tweak" argument. If specified as true, "tweak" means to tweak the specified "listener" name for uniqueness. This avoids LLEventPump::listen()'s DupListenerName exception, which causes the "listen" operation to return "status" as false. --- indra/llcommon/llleaplistener.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp index 55e4752c5d..e8a4775b67 100644 --- a/indra/llcommon/llleaplistener.cpp +++ b/indra/llcommon/llleaplistener.cpp @@ -80,11 +80,13 @@ LLLeapListener::LLLeapListener(const std::string_view& caller, const Callback& c add("listen", "Listen to an existing LLEventPump named [\"source\"], with listener name\n" "[\"listener\"].\n" + "If [\"tweak\"] is specified as true, tweak listener name for uniqueness.\n" "By default, send events on [\"source\"] to the plugin, decorated\n" "with [\"pump\"]=[\"source\"].\n" "If [\"dest\"] specified, send undecorated events on [\"source\"] to the\n" "LLEventPump named [\"dest\"].\n" - "Returns [\"status\"] boolean indicating whether the connection was made.", + "Returns [\"status\"] boolean indicating whether the connection was made,\n" + "plus [\"listener\"] reporting (possibly tweaked) listener name.", &LLLeapListener::listen, need_source_listener); add("stoplistening", @@ -119,6 +121,7 @@ LLLeapListener::~LLLeapListener() // value_type, and Bad Things would happen if you copied an // LLTempBoundListener. (Destruction of the original would disconnect the // listener, invalidating every stored connection.) + LL_DEBUGS("LLLeapListener") << "~LLLeapListener(\"" << mCaller << "\")" << LL_ENDL; for (ListenersMap::value_type& pair : mListeners) { pair.second.disconnect(); @@ -155,6 +158,11 @@ void LLLeapListener::listen(const LLSD& request) std::string source_name = request["source"]; std::string dest_name = request["dest"]; std::string listener_name = request["listener"]; + if (request["tweak"].asBoolean()) + { + listener_name = LLEventPump::inventName(listener_name); + } + reply["listener"] = listener_name; LLEventPump & source = LLEventPumps::instance().obtain(source_name); -- cgit v1.2.3 From 1d1a278712298b91342b687d1b15b107ad51b4ba Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 2 Apr 2024 11:16:29 -0400 Subject: Add LL.source_path(), source_dir() Lua entry points. This helps a Lua script log its own identity, or find associated files relative to its location in the filesystem. Add more comprehensive logging around the start and end of a given Lua script, or its "p.s." fiber.run() call. --- indra/llcommon/lua_function.cpp | 45 +++++++++++++++++++++++++++++++++++++++-- indra/llcommon/lua_function.h | 3 +++ 2 files changed, 46 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index e731408c7d..96282df554 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -68,6 +68,15 @@ int lluau::loadstring(lua_State *L, const std::string &desc, const std::string & return luau_load(L, desc.data(), bytecode.get(), bytecodeSize, 0); } +std::filesystem::path lluau::source_path(lua_State* L) +{ + //Luau lua_Debug and lua_getinfo() are different compared to default Lua: + //see https://github.com/luau-lang/luau/blob/80928acb92d1e4b6db16bada6d21b1fb6fa66265/VM/include/lua.h + lua_Debug ar; + lua_getinfo(L, 1, "s", &ar); + return ar.source; +} + /***************************************************************************** * Lua <=> C++ conversions *****************************************************************************/ @@ -484,11 +493,15 @@ bool LuaState::checkLua(const std::string& desc, int r) std::pair LuaState::expr(const std::string& desc, const std::string& text) { if (! checkLua(desc, lluau::dostring(mState, desc, text))) + { + LL_WARNS("Lua") << desc << " error: " << mError << LL_ENDL; return { -1, mError }; + } // here we believe there was no error -- did the Lua fragment leave // anything on the stack? std::pair result{ lua_gettop(mState), {} }; + LL_INFOS("Lua") << desc << " done, " << result.first << " results." << LL_ENDL; if (result.first) { // aha, at least one entry on the stack! @@ -501,6 +514,7 @@ std::pair LuaState::expr(const std::string& desc, const std::string& } catch (const std::exception& error) { + LL_WARNS("Lua") << desc << " error converting result: " << error.what() << LL_ENDL; // 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() @@ -519,15 +533,18 @@ std::pair LuaState::expr(const std::string& desc, const std::string& else { // multiple entries on the stack + int index; try { - for (int index = 1; index <= result.first; ++index) + for (index = 1; index <= result.first; ++index) { result.second.append(lua_tollsd(mState, index)); } } catch (const std::exception& error) { + LL_WARNS("Lua") << desc << " error converting result " << index << ": " + << error.what() << LL_ENDL; // see above comments regarding lua_State's error status initLuaState(); return { -1, stringize(LLError::Log::classname(error), ": ", error.what()) }; @@ -577,9 +594,13 @@ std::pair LuaState::expr(const std::string& desc, const std::string& { // 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; + LL_INFOS("Lua") << desc << " p.s. fiber.run()" << LL_ENDL; if (! checkLua(desc, lua_pcall(mState, 0, 0, 0))) + { + LL_WARNS("Lua") << desc << " p.s. fiber.run() error: " << mError << LL_ENDL; return { -1, mError }; + } + LL_INFOS("Lua") << desc << " p.s. done." << LL_ENDL; } } // pop everything again @@ -684,6 +705,26 @@ std::pair LuaFunction::getState() return { registry, lookup }; } +/***************************************************************************** +* source_path() +*****************************************************************************/ +lua_function(source_path, "return the source path of the running Lua script") +{ + luaL_checkstack(L, 1, nullptr); + lua_pushstdstring(L, lluau::source_path(L)); + return 1; +} + +/***************************************************************************** +* source_dir() +*****************************************************************************/ +lua_function(source_dir, "return the source directory of the running Lua script") +{ + luaL_checkstack(L, 1, nullptr); + lua_pushstdstring(L, lluau::source_path(L).parent_path()); + return 1; +} + /***************************************************************************** * check_stop() *****************************************************************************/ diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 868c13c3f1..785aadeb0c 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -18,6 +18,7 @@ #include "luau/lualib.h" #include "stringize.h" #include // std::uncaught_exceptions() +#include #include // std::shared_ptr #include // std::pair @@ -49,6 +50,8 @@ namespace lluau // terminated char arrays. int dostring(lua_State* L, const std::string& desc, const std::string& text); int loadstring(lua_State* L, const std::string& desc, const std::string& text); + + std::filesystem::path source_path(lua_State* L); } // namespace lluau std::string lua_tostdstring(lua_State* L, int index); -- cgit v1.2.3 From b610b378ee3249b572d98875a0e557cbf80c2ded Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 2 Apr 2024 11:21:13 -0400 Subject: Use LLCoros::TempStatus when Lua is waiting on get_event_next(). When enumerating C++ coroutines, it can be useful to know that a particular Lua coroutine is simply waiting for further events. --- indra/llcommon/lualistener.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'indra') diff --git a/indra/llcommon/lualistener.cpp b/indra/llcommon/lualistener.cpp index 018a31d5a8..82e32860db 100644 --- a/indra/llcommon/lualistener.cpp +++ b/indra/llcommon/lualistener.cpp @@ -104,6 +104,7 @@ LuaListener::PumpData LuaListener::getNext() { try { + LLCoros::TempStatus status("get_event_next()"); return mQueue.pop(); } catch (const LLThreadSafeQueueInterrupt&) -- cgit v1.2.3 From cc8299d153b9aa81558951cb6b1a19693fb52982 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 2 Apr 2024 11:22:45 -0400 Subject: Streamline std::filesystem::path conversions in LLRequireResolver. Make LLRequireResolver capture std::filesystem::path instances, instead of std::strings, for the path to resolve and the source directory. Store the running script's containing directory instead of calling parent_path() over and over. Demote Lua LL.post_on() logging to DEBUG level instead of INFO. --- indra/newview/llluamanager.cpp | 29 +++++++++++------------------ indra/newview/llluamanager.h | 5 +++-- 2 files changed, 14 insertions(+), 20 deletions(-) (limited to 'indra') diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index f3a8d2c51c..9059db9967 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -122,7 +122,7 @@ lua_function(post_on, "post_on(pumpname, data): post specified data to specified std::string pumpname{ lua_tostdstring(L, 1) }; LLSD data{ lua_tollsd(L, 2) }; lua_pop(L, 2); - LL_INFOS("Lua") << "post_on('" << pumpname << "', " << data << ")" << LL_ENDL; + LL_DEBUGS("Lua") << "post_on('" << pumpname << "', " << data << ")" << LL_ENDL; LLEventPumps::instance().obtain(pumpname).post(data); return 0; } @@ -314,19 +314,12 @@ void LLRequireResolver::resolveRequire(lua_State *L, std::string path) } LLRequireResolver::LLRequireResolver(lua_State *L, const std::string& path) : - mPathToResolve(path), + mPathToResolve(std::filesystem::path(path).lexically_normal()), L(L) { - //Luau lua_Debug and lua_getinfo() are different compared to default Lua: - //see https://github.com/luau-lang/luau/blob/80928acb92d1e4b6db16bada6d21b1fb6fa66265/VM/include/lua.h - lua_Debug ar; - lua_getinfo(L, 1, "s", &ar); - mSourceChunkname = ar.source; + mSourceDir = lluau::source_path(L).parent_path(); - std::filesystem::path fs_path(mPathToResolve); - mPathToResolve = fs_path.lexically_normal().string(); - - if (fs_path.is_absolute()) + if (mPathToResolve.is_absolute()) luaL_argerrorL(L, 1, "cannot require a full path"); } @@ -358,8 +351,8 @@ private: // push the loaded module or throw a Lua error void LLRequireResolver::findModule() { - // If mPathToResolve is absolute, this replaces mSourceChunkname.parent_path. - auto absolutePath = (std::filesystem::path((mSourceChunkname)).parent_path() / mPathToResolve).u8string(); + // If mPathToResolve is absolute, this replaces mSourceDir. + auto absolutePath = (mSourceDir / mPathToResolve).u8string(); // Push _MODULES table on stack for checking and saving to the cache luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1); @@ -375,16 +368,16 @@ void LLRequireResolver::findModule() // not already cached - prep error message just in case auto fail{ - [L=L, path=mPathToResolve]() + [L=L, path=mPathToResolve.u8string()]() { luaL_error(L, "could not find require('%s')", path.data()); }}; - if (std::filesystem::path(mPathToResolve).is_absolute()) + if (mPathToResolve.is_absolute()) { // no point searching known directories for an absolute path fail(); } - std::vector lib_paths + std::vector lib_paths { gDirUtilp->getExpandedFilename(LL_PATH_SCRIPTS, "lua"), #ifdef LL_TEST @@ -395,10 +388,10 @@ void LLRequireResolver::findModule() for (const auto& path : lib_paths) { - std::string absolutePathOpt = (std::filesystem::path(path) / mPathToResolve).u8string(); + std::string absolutePathOpt = (path / mPathToResolve).u8string(); if (absolutePathOpt.empty()) - luaL_error(L, "error requiring module '%s'", mPathToResolve.data()); + luaL_error(L, "error requiring module '%s'", mPathToResolve.u8string().data()); if (findModuleImpl(absolutePathOpt)) return; diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h index a297d14502..bd581376b4 100644 --- a/indra/newview/llluamanager.h +++ b/indra/newview/llluamanager.h @@ -29,6 +29,7 @@ #include "llcoros.h" #include "llsd.h" +#include #include #include #include // std::pair @@ -88,8 +89,8 @@ class LLRequireResolver static void resolveRequire(lua_State *L, std::string path); private: - std::string mPathToResolve; - std::string mSourceChunkname; + std::filesystem::path mPathToResolve; + std::filesystem::path mSourceDir; LLRequireResolver(lua_State *L, const std::string& path); -- cgit v1.2.3 From 1866d1a450b7857da82ceafbd2c6581d87b7f334 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 2 Apr 2024 11:29:29 -0400 Subject: Add startup.lua module with startup.ensure(), wait() functions. This lets a calling script verify that it's running at the right point in the viewer's life cycle. A script that wants to interact with the SL agent wouldn't work if run from the viewer's command line -- unless it calls startup.wait("STATE_STARTED"), which pauses until login is complete. Modify test_luafloater_demo.lua and test_luafloater_gesture_list.lua to find their respective floater XUI files in the same directory as themselves. Make them both capture the reqid returned by the "showLuaFloater" operation, and filter for events bearing the same reqid. This paves the way for a given script to display more than one floater concurrently. Make test_luafloater_demo.lua (which does not require in-world resources) wait until 'STATE_LOGIN_WAIT', the point at which the viewer has presented the login screen. Make test_luafloater_gesture_list.lua (which interacts with the agent) wait until 'STATE_STARTED', the point at which the viewer is fully in world. Either or both can now be launched from the viewer's command line. --- indra/newview/scripts/lua/startup.lua | 101 +++++++++++++++++++++ indra/newview/scripts/lua/test_luafloater_demo.lua | 25 +++-- .../scripts/lua/test_luafloater_gesture_list.lua | 26 ++++-- 3 files changed, 138 insertions(+), 14 deletions(-) create mode 100644 indra/newview/scripts/lua/startup.lua (limited to 'indra') diff --git a/indra/newview/scripts/lua/startup.lua b/indra/newview/scripts/lua/startup.lua new file mode 100644 index 0000000000..4311bb9a60 --- /dev/null +++ b/indra/newview/scripts/lua/startup.lua @@ -0,0 +1,101 @@ +-- query, wait for or mandate a particular viewer startup state + +-- During startup, the viewer steps through a sequence of numbered (and named) +-- states. This can be used to detect when, for instance, the login screen is +-- displayed, or when the viewer has finished logging in and is fully +-- in-world. + +local fiber = require 'fiber' +local leap = require 'leap' +local inspect = require 'inspect' +local function dbg(...) end +-- local dbg = require 'printf' + +-- --------------------------------------------------------------------------- +-- Get the list of startup states from the viewer. +local bynum = leap.request('LLStartUp', {op='getStateTable'})['table'] + +local byname = setmetatable( + {}, + -- set metatable to throw an error if you look up invalid state name + {__index=function(t, k) + local v = t[k] + if v then + return v + end + error(string.format('startup module passed invalid state %q', k), 2) + end}) + +-- derive byname as a lookup table to find the 0-based index for a given name +for i, name in pairs(bynum) do + -- the viewer's states are 0-based, not 1-based like Lua indexes + byname[name] = i - 1 +end +-- dbg('startup states: %s', inspect(byname)) + +-- specialize a WaitFor to track the viewer's startup state +local startup_pump = 'StartupState' +local waitfor = leap.WaitFor:new(0, startup_pump) +function waitfor:filter(pump, data) + if pump == self.name then + return data + end +end + +function waitfor:process(data) + -- keep updating startup._state for interested parties + startup._state = data.str + dbg('startup updating state to %q', data.str) + -- now pass data along to base-class method to queue + leap.WaitFor.process(self, data) +end + +-- listen for StartupState events +leap.request(leap.cmdpump(), + {op='listen', source=startup_pump, listener='startup.lua', tweak=true}) +-- poke LLStartUp to make sure we get an event +leap.send('LLStartUp', {op='postStartupState'}) + +-- --------------------------------------------------------------------------- +startup = {} + +-- wait for response from postStartupState +while not startup._state do + dbg('startup.state() waiting for first StartupState event') + waitfor:wait() +end + +-- return a list of all known startup states +function startup.list() + return bynum +end + +-- report whether state with string name 'left' is before string name 'right' +function startup.before(left, right) + return byname[left] < byname[right] +end + +-- report the viewer's current startup state +function startup.state() + return startup._state +end + +-- error if script is called before specified state string name +function startup.ensure(state) + if startup.before(startup.state(), state) then + -- tell error() to pretend this error was thrown by our caller + error('must not be called before startup state ' .. state, 2) + end +end + +-- block calling fiber until viewer has reached state with specified string name +function startup.wait(state) + dbg('startup.wait(%q)', state) + while startup.before(startup.state(), state) do + local item = waitfor:wait() + dbg('startup.wait(%q) sees %s', state, item) + end +end + +return startup + diff --git a/indra/newview/scripts/lua/test_luafloater_demo.lua b/indra/newview/scripts/lua/test_luafloater_demo.lua index c375a2abc7..c2d16d4f88 100644 --- a/indra/newview/scripts/lua/test_luafloater_demo.lua +++ b/indra/newview/scripts/lua/test_luafloater_demo.lua @@ -1,10 +1,16 @@ -XML_FILE_PATH = "luafloater_demo.xml" +XML_FILE_PATH = LL.source_dir() .. "/luafloater_demo.xml" + +scriptparts = string.split(LL.source_path(), '/') +scriptname = scriptparts[#scriptparts] +print('Running ' .. scriptname) leap = require 'leap' fiber = require 'fiber' +startup = require 'startup' --event pump for sending actions to the floater -COMMAND_PUMP_NAME = "" +local COMMAND_PUMP_NAME = "" +local reqid --table of floater UI events event_list=leap.request("LLFloaterReg", {op="getFloaterEvents"}).events @@ -41,24 +47,29 @@ function handleEvents(event_data) end elseif event_data.event == _event("floater_close") then LL.print_warning("Floater was closed") - leap.done() + return false end + return true end +startup.wait('STATE_LOGIN_WAIT') local key = {xml_path = XML_FILE_PATH, op = "showLuaFloater"} --sign for additional events for defined control {= {action1, action2, ...}} key.extra_events={show_time_lbl = {_event("right_mouse_down"), _event("double_click")}} -COMMAND_PUMP_NAME = leap.request("LLFloaterReg", key).command_name +local resp = leap.request("LLFloaterReg", key) +COMMAND_PUMP_NAME = resp.command_name +reqid = resp.reqid catch_events = leap.WaitFor:new(-1, "all_events") function catch_events:filter(pump, data) - return data + if data.reqid == reqid then + return data + end end function process_events(waitfor) event_data = waitfor:wait() - while event_data do - handleEvents(event_data) + while event_data and handleEvents(event_data) do event_data = waitfor:wait() end end diff --git a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua index 6d4a8e0cad..a907eb2e90 100644 --- a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua +++ b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua @@ -1,11 +1,17 @@ -XML_FILE_PATH = "luafloater_gesture_list.xml" +XML_FILE_PATH = LL.source_dir() .. "/luafloater_gesture_list.xml" + +scriptparts = string.split(LL.source_path(), '/') +scriptname = scriptparts[#scriptparts] +print('Running ' .. scriptname) leap = require 'leap' fiber = require 'fiber' LLGesture = require 'LLGesture' +startup = require 'startup' --event pump for sending actions to the floater -COMMAND_PUMP_NAME = "" +local COMMAND_PUMP_NAME = "" +local reqid --table of floater UI events event_list=leap.request("LLFloaterReg", {op="getFloaterEvents"}).events @@ -22,9 +28,12 @@ end function handleEvents(event_data) if event_data.event == _event("floater_close") then - leap.done() - elseif event_data.event == _event("post_build") then + return false + end + + if event_data.event == _event("post_build") then COMMAND_PUMP_NAME = event_data.command_name + reqid = event_data.reqid gestures_uuid = LLGesture.getActiveGestures() local action_data = {} action_data.action = "add_list_element" @@ -40,8 +49,10 @@ function handleEvents(event_data) LLGesture.startGesture(event_data.value) end end + return true end +startup.wait('STATE_STARTED') local key = {xml_path = XML_FILE_PATH, op = "showLuaFloater"} --receive additional events for defined control {= {action1, action2, ...}} key.extra_events={gesture_list = {_event("double_click")}} @@ -49,13 +60,14 @@ handleEvents(leap.request("LLFloaterReg", key)) catch_events = leap.WaitFor:new(-1, "all_events") function catch_events:filter(pump, data) - return data + if data.reqid == reqid then + return data + end end function process_events(waitfor) event_data = waitfor:wait() - while event_data do - handleEvents(event_data) + while event_data and handleEvents(event_data) do event_data = waitfor:wait() end end -- cgit v1.2.3 From 7049485ebd0b997a097c12e094425d58db56e043 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 2 Apr 2024 13:25:08 -0400 Subject: Fix std::filesystem::path - to - std::string conversions on Windows. On Windows, std::filesystem::path::value_type is wchar_t, not char -- so path::string_type is std::wstring, not std::string. So while Posix path instances implicitly convert to string, Windows path instances do not. Add explicit u8string() calls. Also add LL.abspath() Lua entry point to further facilitate finding a resource file relative to the calling Lua script. Use abspath() for both test_luafloater_demo.lua and test_luafloater_gesture_list.lua. --- indra/llcommon/lua_function.cpp | 16 ++++++++++++++-- indra/newview/scripts/lua/test_luafloater_demo.lua | 2 +- .../newview/scripts/lua/test_luafloater_gesture_list.lua | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 96282df554..cd83f40e85 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -711,7 +711,7 @@ std::pair LuaFunction::getState() lua_function(source_path, "return the source path of the running Lua script") { luaL_checkstack(L, 1, nullptr); - lua_pushstdstring(L, lluau::source_path(L)); + lua_pushstdstring(L, lluau::source_path(L).u8string()); return 1; } @@ -721,7 +721,19 @@ lua_function(source_path, "return the source path of the running Lua script") lua_function(source_dir, "return the source directory of the running Lua script") { luaL_checkstack(L, 1, nullptr); - lua_pushstdstring(L, lluau::source_path(L).parent_path()); + lua_pushstdstring(L, lluau::source_path(L).parent_path().u8string()); + return 1; +} + +/***************************************************************************** +* abspath() +*****************************************************************************/ +lua_function(abspath, + "for given filesystem path relative to running script, return absolute path") +{ + auto path{ lua_tostdstring(L, 1) }; + lua_pop(L, 1); + lua_pushstdstring(L, (lluau::source_path(L).parent_path() / path).u8string()); return 1; } diff --git a/indra/newview/scripts/lua/test_luafloater_demo.lua b/indra/newview/scripts/lua/test_luafloater_demo.lua index c2d16d4f88..ab638dcdd1 100644 --- a/indra/newview/scripts/lua/test_luafloater_demo.lua +++ b/indra/newview/scripts/lua/test_luafloater_demo.lua @@ -1,4 +1,4 @@ -XML_FILE_PATH = LL.source_dir() .. "/luafloater_demo.xml" +XML_FILE_PATH = LL.abspath("luafloater_demo.xml") scriptparts = string.split(LL.source_path(), '/') scriptname = scriptparts[#scriptparts] diff --git a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua index a907eb2e90..3d9a9b0ad4 100644 --- a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua +++ b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua @@ -1,4 +1,4 @@ -XML_FILE_PATH = LL.source_dir() .. "/luafloater_gesture_list.xml" +XML_FILE_PATH = LL.abspath("luafloater_gesture_list.xml") scriptparts = string.split(LL.source_path(), '/') scriptname = scriptparts[#scriptparts] -- cgit v1.2.3 From b351888ed7d395279dfc022363e911d52ebdcc16 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Wed, 3 Apr 2024 14:26:34 +0300 Subject: Add RAII class for adding/erasing script entries; code clean up --- indra/llwindow/llwindowwin32.cpp | 4 +--- indra/llwindow/llwindowwin32.h | 2 +- indra/newview/llfloaterluascripts.cpp | 7 ++----- indra/newview/llfloaterluascripts.h | 2 +- indra/newview/llluamanager.cpp | 4 +--- indra/newview/llluamanager.h | 18 +++++++++++++++++- 6 files changed, 23 insertions(+), 14 deletions(-) (limited to 'indra') diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index e3ef28a27d..7fbc2c8ea2 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -3757,9 +3757,7 @@ S32 OSMessageBoxWin32(const std::string& text, const std::string& caption, U32 t void shell_open(const std::string &file, bool async) { - // this is madness.. no, this is.. - LLWString url_wstring = utf8str_to_wstring(file); - llutf16string url_utf16 = wstring_to_utf16str(url_wstring); + std::wstring url_utf16 = ll_convert(file); // let the OS decide what to use to open the URL SHELLEXECUTEINFO sei = {sizeof(sei)}; diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h index ed64891108..320c1c8b88 100644 --- a/indra/llwindow/llwindowwin32.h +++ b/indra/llwindow/llwindowwin32.h @@ -122,7 +122,7 @@ public: /*virtual*/ void interruptLanguageTextInput(); /*virtual*/ void spawnWebBrowser(const std::string& escaped_url, bool async); - void openFolder(const std::string &path); + void openFolder(const std::string &path) override; /*virtual*/ F32 getSystemUISize(); diff --git a/indra/newview/llfloaterluascripts.cpp b/indra/newview/llfloaterluascripts.cpp index 87d2cf0c69..bd845a97d6 100644 --- a/indra/newview/llfloaterluascripts.cpp +++ b/indra/newview/llfloaterluascripts.cpp @@ -51,7 +51,7 @@ LLFloaterLUAScripts::LLFloaterLUAScripts(const LLSD &key) BOOL LLFloaterLUAScripts::postBuild() { mScriptList = getChild("scripts_list"); - mScriptList->setRightMouseDownCallback(boost::bind(&LLFloaterLUAScripts::onScrollListRightClicked, this, _1, _2, _3)); + mScriptList->setRightMouseDownCallback([this](LLUICtrl *ctrl, S32 x, S32 y, MASK mask) { onScrollListRightClicked(ctrl, x, y);}); LLContextMenu *menu = LLUICtrlFactory::getInstance()->createFromFile( "menu_lua_scripts.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); @@ -71,13 +71,11 @@ LLFloaterLUAScripts::~LLFloaterLUAScripts() menu->die(); mContextMenuHandle.markDead(); } - - delete mUpdateTimer; } void LLFloaterLUAScripts::draw() { - if (mUpdateTimer->hasExpired()) + if (mUpdateTimer->checkExpirationAndReset(REFRESH_INTERVAL)) { populateScriptList(); } @@ -103,7 +101,6 @@ void LLFloaterLUAScripts::populateScriptList() } mScriptList->setScrollPos(prev_pos); mScriptList->setSelectedByValue(prev_selected, true); - mUpdateTimer->setTimerExpirySec(REFRESH_INTERVAL); } void LLFloaterLUAScripts::onScrollListRightClicked(LLUICtrl *ctrl, S32 x, S32 y) diff --git a/indra/newview/llfloaterluascripts.h b/indra/newview/llfloaterluascripts.h index c8c9e7f020..548bbd10f6 100644 --- a/indra/newview/llfloaterluascripts.h +++ b/indra/newview/llfloaterluascripts.h @@ -44,7 +44,7 @@ private: void populateScriptList(); void onScrollListRightClicked(LLUICtrl *ctrl, S32 x, S32 y); - LLTimer* mUpdateTimer; + std::unique_ptr mUpdateTimer; LLScrollListCtrl* mScriptList; std::string mTargetFolderPath; diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 343c7c7459..d2b8ca3a94 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -179,8 +179,7 @@ void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn r // A script_result_fn will be called when LuaState::expr() completes. LLCoros::instance().launch(filename, [filename, result_cb, finished_cb]() { - std::string coro_name = LLCoros::getName(); - sScriptNames[coro_name] = filename; + ScriptObserver observer(LLCoros::getName(), filename); llifstream in_file; in_file.open(filename.c_str()); @@ -205,7 +204,6 @@ void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn r result_cb(-1, msg); } } - sScriptNames.erase(coro_name); }); } diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h index 70728f958e..88467cbf84 100644 --- a/indra/newview/llluamanager.h +++ b/indra/newview/llluamanager.h @@ -40,6 +40,8 @@ class LuaState; class LLLUAmanager { + friend class ScriptObserver; + 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. @@ -81,7 +83,7 @@ public: static void runScriptOnLogin(); - static std::map getScriptNames() { return sScriptNames; } + static const std::map getScriptNames() { return sScriptNames; } private: static std::map sScriptNames; @@ -104,4 +106,18 @@ class LLRequireResolver bool findModuleImpl(const std::string& absolutePath); void runModule(const std::string& desc, const std::string& code); }; + +// RAII class to guarantee that a script entry is erased even when coro is terminated +class ScriptObserver +{ + public: + ScriptObserver(const std::string &coro_name, const std::string &filename) : mCoroName(coro_name) + { + LLLUAmanager::sScriptNames[mCoroName] = filename; + } + ~ScriptObserver() { LLLUAmanager::sScriptNames.erase(mCoroName); } + + private: + std::string mCoroName; +}; #endif -- cgit v1.2.3 From 3b25bc10febc84f10348715dabc9590458923c0b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 3 Apr 2024 11:07:36 -0400 Subject: Make ll_convert() and ll_convert_to() use std::decay_t on arg type. Among other things, this empowers ll_convert() and ll_convert_to() to accept a string literal (which might contain non-ASCII characters, e.g. __FILE__). Without this, even though we have ll_convert_impl specializations accepting const char*, passing a string literal fails because the compiler can't find a specialization specifically accepting const char[length]. --- indra/llcommon/llstring.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 0eb2770004..14aa51cb4a 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -37,6 +37,7 @@ #include #include #include +#include #include "llformat.h" #include "stdtypes.h" @@ -542,7 +543,7 @@ public: template inline operator TO() const { - return ll_convert_impl()(mRef); + return ll_convert_impl>()(mRef); } }; @@ -551,7 +552,7 @@ public: template TO ll_convert_to(const FROM& in) { - return ll_convert_impl()(in); + return ll_convert_impl>()(in); } // degenerate case -- cgit v1.2.3 From e399b02e3306a249cb161f07cac578d3f2617bab Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 3 Apr 2024 12:31:43 -0400 Subject: Introduce fsyspath subclass of std::filesystem::path. Our std::strings are UTF-8 encoded, so conversion from std::string to std::filesystem::path must use UTF-8 decoding. The native Windows std::filesystem::path constructor and assignment operator accepting std::string use "native narrow encoding," which mangles path strings containing UTF-8 encoded non-ASCII characters. fsyspath's std::string constructor and assignment operator explicitly engage std::filesystem::u8path() to handle encoding. u8path() is deprecated in C++20, but once we adapt fsyspath's conversion to C++20 conventions, consuming code need not be modified. --- indra/llcommon/CMakeLists.txt | 1 + indra/llcommon/fsyspath.h | 74 +++++++++++++++++++++++++++++++++++++++++ indra/llcommon/lua_function.cpp | 6 ++-- indra/llcommon/lua_function.h | 4 +-- indra/llui/llluafloater.cpp | 8 ++--- indra/newview/llluamanager.cpp | 8 ++--- indra/newview/llluamanager.h | 6 ++-- 7 files changed, 91 insertions(+), 16 deletions(-) create mode 100644 indra/llcommon/fsyspath.h (limited to 'indra') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index aed9ee080b..aa0b66f2f4 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -127,6 +127,7 @@ set(llcommon_HEADER_FILES commoncontrol.h ctype_workaround.h fix_macros.h + fsyspath.h function_types.h indra_constants.h lazyeventapi.h diff --git a/indra/llcommon/fsyspath.h b/indra/llcommon/fsyspath.h new file mode 100644 index 0000000000..aa4e0132bc --- /dev/null +++ b/indra/llcommon/fsyspath.h @@ -0,0 +1,74 @@ +/** + * @file fsyspath.h + * @author Nat Goodspeed + * @date 2024-04-03 + * @brief Adapt our UTF-8 std::strings for std::filesystem::path + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_FSYSPATH_H) +#define LL_FSYSPATH_H + +#include + +// While std::filesystem::path can be directly constructed from std::string on +// both Posix and Windows, that's not what we want on Windows. Per +// https://en.cppreference.com/w/cpp/filesystem/path/path: + +// ... the method of conversion to the native character set depends on the +// character type used by source. +// +// * If the source character type is char, the encoding of the source is +// assumed to be the native narrow encoding (so no conversion takes place on +// POSIX systems). +// * If the source character type is char8_t, conversion from UTF-8 to native +// filesystem encoding is used. (since C++20) +// * If the source character type is wchar_t, the input is assumed to be the +// native wide encoding (so no conversion takes places on Windows). + +// The trouble is that on Windows, from std::string ("source character type is +// char"), the "native narrow encoding" isn't UTF-8, so file paths containing +// non-ASCII characters get mangled. +// +// Once we're building with C++20, we could pass a UTF-8 std::string through a +// vector to engage std::filesystem::path's own UTF-8 conversion. But +// sigh, as of 2024-04-03 we're not yet there. +// +// Anyway, encapsulating the important UTF-8 conversions in our own subclass +// allows us to migrate forward to C++20 conventions without changing +// referencing code. + +class fsyspath: public std::filesystem::path +{ + using super = std::filesystem::path; + +public: + // default + fsyspath() {} + // construct from UTF-8 encoded std::string + fsyspath(const std::string& path): super(std::filesystem::u8path(path)) {} + // construct from UTF-8 encoded const char* + fsyspath(const char* path): super(std::filesystem::u8path(path)) {} + // construct from existing path + fsyspath(const super& path): super(path) {} + + fsyspath& operator=(const super& p) { super::operator=(p); return *this; } + fsyspath& operator=(const std::string& p) + { + super::operator=(std::filesystem::u8path(p)); + return *this; + } + fsyspath& operator=(const char* p) + { + super::operator=(std::filesystem::u8path(p)); + return *this; + } + + // shadow base-class string() method with UTF-8 aware method + std::string string() const { return super::u8string(); } +}; + +#endif /* ! defined(LL_FSYSPATH_H) */ diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index cd83f40e85..7a5668f384 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -17,13 +17,13 @@ // std headers #include #include -#include #include // std::quoted #include #include // std::unique_ptr #include // external library headers // other Linden headers +#include "fsyspath.h" #include "hexdump.h" #include "lleventcoro.h" #include "llsd.h" @@ -68,7 +68,7 @@ int lluau::loadstring(lua_State *L, const std::string &desc, const std::string & return luau_load(L, desc.data(), bytecode.get(), bytecodeSize, 0); } -std::filesystem::path lluau::source_path(lua_State* L) +fsyspath lluau::source_path(lua_State* L) { //Luau lua_Debug and lua_getinfo() are different compared to default Lua: //see https://github.com/luau-lang/luau/blob/80928acb92d1e4b6db16bada6d21b1fb6fa66265/VM/include/lua.h @@ -577,7 +577,7 @@ std::pair LuaState::expr(const std::string& desc, const std::string& // 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") + fsyspath(lua_tostdstring(mState, -2)).stem() == "fiber") { found = true; break; diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 785aadeb0c..ec1e6cdb10 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -16,9 +16,9 @@ #include "luau/lua.h" #include "luau/luaconf.h" #include "luau/lualib.h" +#include "fsyspath.h" #include "stringize.h" #include // std::uncaught_exceptions() -#include #include // std::shared_ptr #include // std::pair @@ -51,7 +51,7 @@ namespace lluau int dostring(lua_State* L, const std::string& desc, const std::string& text); int loadstring(lua_State* L, const std::string& desc, const std::string& text); - std::filesystem::path source_path(lua_State* L); + fsyspath source_path(lua_State* L); } // namespace lluau std::string lua_tostdstring(lua_State* L, int index); diff --git a/indra/llui/llluafloater.cpp b/indra/llui/llluafloater.cpp index 268075b05d..e584a67a00 100644 --- a/indra/llui/llluafloater.cpp +++ b/indra/llui/llluafloater.cpp @@ -26,7 +26,7 @@ #include "llluafloater.h" -#include +#include "fsyspath.h" #include "llevents.h" #include "llcheckboxctrl.h" @@ -271,12 +271,12 @@ void LLLuaFloater::postEvent(LLSD data, const std::string &event_name) void LLLuaFloater::showLuaFloater(const LLSD &data) { - std::filesystem::path fs_path(data["xml_path"].asString()); - std::string path = fs_path.lexically_normal().string(); + fsyspath fs_path(data["xml_path"].asString()); + std::string path = fs_path.lexically_normal().u8string(); if (!fs_path.is_absolute()) { std::string lib_path = gDirUtilp->getExpandedFilename(LL_PATH_SCRIPTS, "lua"); - path = (std::filesystem::path(lib_path) / path).u8string(); + path = (fsyspath(lib_path) / path).u8string(); } LLLuaFloater *floater = new LLLuaFloater(data); diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 9059db9967..82be85a153 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -28,6 +28,7 @@ #include "llviewerprecompiledheaders.h" #include "llluamanager.h" +#include "fsyspath.h" #include "llcoros.h" #include "llerror.h" #include "lleventcoro.h" @@ -37,7 +38,6 @@ #include "stringize.h" #include -#include #include "luau/luacode.h" #include "luau/lua.h" @@ -314,7 +314,7 @@ void LLRequireResolver::resolveRequire(lua_State *L, std::string path) } LLRequireResolver::LLRequireResolver(lua_State *L, const std::string& path) : - mPathToResolve(std::filesystem::path(path).lexically_normal()), + mPathToResolve(fsyspath(path).lexically_normal()), L(L) { mSourceDir = lluau::source_path(L).parent_path(); @@ -377,12 +377,12 @@ void LLRequireResolver::findModule() fail(); } - std::vector lib_paths + std::vector lib_paths { gDirUtilp->getExpandedFilename(LL_PATH_SCRIPTS, "lua"), #ifdef LL_TEST // Build-time tests don't have the app bundle - use source tree. - std::filesystem::path(__FILE__).parent_path() / "scripts" / "lua", + fsyspath(__FILE__).parent_path() / "scripts" / "lua", #endif }; diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h index bd581376b4..3c00450179 100644 --- a/indra/newview/llluamanager.h +++ b/indra/newview/llluamanager.h @@ -27,9 +27,9 @@ #ifndef LL_LLLUAMANAGER_H #define LL_LLLUAMANAGER_H +#include "fsyspath.h" #include "llcoros.h" #include "llsd.h" -#include #include #include #include // std::pair @@ -89,8 +89,8 @@ class LLRequireResolver static void resolveRequire(lua_State *L, std::string path); private: - std::filesystem::path mPathToResolve; - std::filesystem::path mSourceDir; + fsyspath mPathToResolve; + fsyspath mSourceDir; LLRequireResolver(lua_State *L, const std::string& path); -- cgit v1.2.3 From 3f876a6a1d138c31266afb6c39df7090e304efe3 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 3 Apr 2024 13:04:21 -0400 Subject: Use raw string literal syntax for LLLeapListener help strings. --- indra/llcommon/llleaplistener.cpp | 42 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp index e8a4775b67..9b9b0f5121 100644 --- a/indra/llcommon/llleaplistener.cpp +++ b/indra/llcommon/llleaplistener.cpp @@ -70,46 +70,46 @@ LLLeapListener::LLLeapListener(const std::string_view& caller, const Callback& c { LLSD need_name(LLSDMap("name", LLSD())); add("newpump", - "Instantiate a new LLEventPump named like [\"name\"] and listen to it.\n" - "[\"type\"] == \"LLEventStream\", \"LLEventMailDrop\" et al.\n" - "Events sent through new LLEventPump will be decorated with [\"pump\"]=name.\n" - "Returns actual name in [\"name\"] (may be different if collision).", +R"-(Instantiate a new LLEventPump named like ["name"] and listen to it. +["type"] == "LLEventStream", "LLEventMailDrop" et al. +Events sent through new LLEventPump will be decorated with ["pump"]=name. +Returns actual name in ["name"] (may be different if collision).)-", &LLLeapListener::newpump, need_name); LLSD need_source_listener(LLSDMap("source", LLSD())("listener", LLSD())); add("listen", - "Listen to an existing LLEventPump named [\"source\"], with listener name\n" - "[\"listener\"].\n" - "If [\"tweak\"] is specified as true, tweak listener name for uniqueness.\n" - "By default, send events on [\"source\"] to the plugin, decorated\n" - "with [\"pump\"]=[\"source\"].\n" - "If [\"dest\"] specified, send undecorated events on [\"source\"] to the\n" - "LLEventPump named [\"dest\"].\n" - "Returns [\"status\"] boolean indicating whether the connection was made,\n" - "plus [\"listener\"] reporting (possibly tweaked) listener name.", +R"-(Listen to an existing LLEventPump named ["source"], with listener name +["listener"]. +If ["tweak"] is specified as true, tweak listener name for uniqueness. +By default, send events on ["source"] to the plugin, decorated +with ["pump"]=["source"]. +If ["dest"] specified, send undecorated events on ["source"] to the +LLEventPump named ["dest"]. +Returns ["status"] boolean indicating whether the connection was made, +plus ["listener"] reporting (possibly tweaked) listener name.)-", &LLLeapListener::listen, need_source_listener); add("stoplistening", - "Disconnect a connection previously established by \"listen\".\n" - "Pass same [\"source\"] and [\"listener\"] arguments.\n" - "Returns [\"status\"] boolean indicating whether such a listener existed.", +R"-(Disconnect a connection previously established by "listen". +Pass same ["source"] and ["listener"] arguments. +Returns ["status"] boolean indicating whether such a listener existed.)-", &LLLeapListener::stoplistening, need_source_listener); add("ping", - "No arguments, just a round-trip sanity check.", +"No arguments, just a round-trip sanity check.", &LLLeapListener::ping); add("getAPIs", - "Enumerate all LLEventAPI instances by name and description.", +"Enumerate all LLEventAPI instances by name and description.", &LLLeapListener::getAPIs); add("getAPI", - "Get name, description, dispatch key and operations for LLEventAPI [\"api\"].", +R"-(Get name, description, dispatch key and operations for LLEventAPI ["api"].)-", &LLLeapListener::getAPI, LLSD().with("api", LLSD())); add("getFeatures", - "Return an LLSD map of feature strings (deltas from baseline LEAP protocol)", +"Return an LLSD map of feature strings (deltas from baseline LEAP protocol)", static_cast(&LLLeapListener::getFeatures)); add("getFeature", - "Return the feature value with key [\"feature\"]", +R"-(Return the feature value with key ["feature"])-", &LLLeapListener::getFeature, LLSD().with("feature", LLSD())); } -- cgit v1.2.3 From 0f2d261bbfa7a827290f3acb7e6f71e29eed3a1b Mon Sep 17 00:00:00 2001 From: Nicky Date: Wed, 3 Apr 2024 21:24:05 +0200 Subject: Proper casing for Lualibs (or case sensitive filesystems do not agree with the filename) --- indra/newview/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index b26ce7d06d..d98b45e471 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -48,7 +48,7 @@ include(VulkanGltf) include(ZLIBNG) include(URIPARSER) include(LLPrimitive) -include(lualibs) +include(Lualibs) if (NOT HAVOK_TPV) # When using HAVOK_TPV, the library is precompiled, so no need for this @@ -2379,4 +2379,3 @@ if (LL_TESTS) endif (LL_TESTS) check_message_template(${VIEWER_BINARY_NAME}) - -- cgit v1.2.3 From c8f7e9d0256cec90d509b0cf0109c2c7479100d0 Mon Sep 17 00:00:00 2001 From: Nicky Date: Wed, 3 Apr 2024 21:24:56 +0200 Subject: - Enable luaulib linking for Linux - Put lubLuau.Ast.a at the right most side as GCC/LD is peculiar about link order. --- indra/cmake/Lualibs.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indra') diff --git a/indra/cmake/Lualibs.cmake b/indra/cmake/Lualibs.cmake index 7edfbfdc3d..d66305a8e5 100644 --- a/indra/cmake/Lualibs.cmake +++ b/indra/cmake/Lualibs.cmake @@ -20,10 +20,10 @@ if (WINDOWS) target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/Luau.Compiler.lib) target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/Luau.Config.lib) target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/Luau.VM.lib) -elseif (DARWIN) - target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/libLuau.Ast.a) +else () target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/libLuau.CodeGen.a) target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/libLuau.Compiler.a) target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/libLuau.Config.a) target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/libLuau.VM.a) -endif (WINDOWS) + target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/libLuau.Ast.a) +endif () -- cgit v1.2.3 From 81d7fae644b759dd7b5620c7469b2941743dced8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 3 Apr 2024 15:52:46 -0400 Subject: Introduce LLInstanceTracker::destroy() methods; use in ~LuaState(). --- indra/llcommon/llinstancetracker.h | 52 ++++++++++++++++++++++++++++++++++++++ indra/llcommon/lua_function.cpp | 11 ++------ 2 files changed, 54 insertions(+), 9 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index 27422e1266..22e5d9c7a7 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -275,6 +275,35 @@ protected: public: virtual const KEY& getKey() const { return mInstanceKey; } + /// for use ONLY for an object we're sure resides on the heap! + static bool destroy(const KEY& key) + { + return destroy(getInstance(key)); + } + + /// for use ONLY for an object we're sure resides on the heap! + static bool destroy(const weak_t& ptr) + { + return destroy(ptr.lock()); + } + + /// for use ONLY for an object we're sure resides on the heap! + static bool destroy(const ptr_t& ptr) + { + if (! ptr) + { + return false; + } + + // Because we store and return ptr_t instances with no-op deleters, + // merely resetting the last pointer doesn't destroy the referenced + // object. Don't even bother resetting 'ptr'. Just extract its raw + // pointer and delete that. + auto raw{ ptr.get() }; + delete raw; + return true; + } + private: LLInstanceTracker( const LLInstanceTracker& ) = delete; LLInstanceTracker& operator=( const LLInstanceTracker& ) = delete; @@ -479,6 +508,29 @@ public: template using key_snapshot_of = instance_snapshot_of; + /// for use ONLY for an object we're sure resides on the heap! + static bool destroy(const weak_t& ptr) + { + return destroy(ptr.lock()); + } + + /// for use ONLY for an object we're sure resides on the heap! + static bool destroy(const ptr_t& ptr) + { + if (! ptr) + { + return false; + } + + // Because we store and return ptr_t instances with no-op deleters, + // merely resetting the last pointer doesn't destroy the referenced + // object. Don't even bother resetting 'ptr'. Just extract its raw + // pointer and delete that. + auto raw{ ptr.get() }; + delete raw; + return true; + } + protected: LLInstanceTracker() { diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 7a5668f384..c86faf1ae2 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -286,7 +286,7 @@ LLSD lua_tollsd(lua_State* L, int index) popper.disarm(); // Table keys are all integers: are they reasonable integers? // Arbitrary max: may bite us, but more likely to protect us - size_t array_max{ 10000 }; + const size_t array_max{ 10000 }; if (keys.size() > array_max) { return lluau::error(L, "Conversion from Lua to LLSD array limited to %d entries", @@ -459,14 +459,7 @@ LuaState::~LuaState() { // Did somebody call obtainListener() on this LuaState? // That is, is there a LuaListener key in its registry? - auto listener{ getListener() }; - if (listener) - { - // if we got a LuaListener instance, destroy it - auto lptr{ listener.get() }; - listener.reset(); - delete lptr; - } + LuaListener::destroy(getListener()); lua_close(mState); -- cgit v1.2.3 From e02ea3ddee87021a4b6fa0de874e2d6d71da65f9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 3 Apr 2024 16:32:21 -0400 Subject: LLInstanceTracker::destruct() instead of destroy(). Avoid ambiguity with LLFloater::destroy(). --- indra/llcommon/llinstancetracker.h | 16 ++++++++-------- indra/llcommon/lua_function.cpp | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index 22e5d9c7a7..921f743ada 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -276,19 +276,19 @@ public: virtual const KEY& getKey() const { return mInstanceKey; } /// for use ONLY for an object we're sure resides on the heap! - static bool destroy(const KEY& key) + static bool destruct(const KEY& key) { - return destroy(getInstance(key)); + return destruct(getInstance(key)); } /// for use ONLY for an object we're sure resides on the heap! - static bool destroy(const weak_t& ptr) + static bool destruct(const weak_t& ptr) { - return destroy(ptr.lock()); + return destruct(ptr.lock()); } /// for use ONLY for an object we're sure resides on the heap! - static bool destroy(const ptr_t& ptr) + static bool destruct(const ptr_t& ptr) { if (! ptr) { @@ -509,13 +509,13 @@ public: using key_snapshot_of = instance_snapshot_of; /// for use ONLY for an object we're sure resides on the heap! - static bool destroy(const weak_t& ptr) + static bool destruct(const weak_t& ptr) { - return destroy(ptr.lock()); + return destruct(ptr.lock()); } /// for use ONLY for an object we're sure resides on the heap! - static bool destroy(const ptr_t& ptr) + static bool destruct(const ptr_t& ptr) { if (! ptr) { diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index c86faf1ae2..332a08a444 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -459,7 +459,7 @@ LuaState::~LuaState() { // Did somebody call obtainListener() on this LuaState? // That is, is there a LuaListener key in its registry? - LuaListener::destroy(getListener()); + LuaListener::destruct(getListener()); lua_close(mState); -- cgit v1.2.3 From 799d1d595505acaa7f05a6d92db5f8f2d258f53e Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Mon, 8 Apr 2024 16:55:26 +0300 Subject: Add script termination option to 'Lua Scripts' floater --- indra/llcommon/llapp.cpp | 1 + indra/llcommon/lualistener.cpp | 8 +++++--- indra/llcommon/lualistener.h | 2 ++ indra/newview/llfloaterluascripts.cpp | 6 ++++++ indra/newview/llfloaterluascripts.h | 1 + indra/newview/llluamanager.cpp | 16 ++++++++++++++++ indra/newview/llluamanager.h | 5 ++++- indra/newview/skins/default/xui/en/menu_lua_scripts.xml | 8 ++++++++ 8 files changed, 43 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index b99166991f..905db9e491 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -429,6 +429,7 @@ void LLApp::setStatus(EAppStatus status) statsd = LLSD::Integer(status); } LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", statsd)); + LLEventPumps::instance().obtain("LLLua").post(llsd::map("status", "close_all")); } } diff --git a/indra/llcommon/lualistener.cpp b/indra/llcommon/lualistener.cpp index 82e32860db..c11ab6b1c3 100644 --- a/indra/llcommon/lualistener.cpp +++ b/indra/llcommon/lualistener.cpp @@ -33,18 +33,20 @@ std::ostream& operator<<(std::ostream& out, const LuaListener& self) LuaListener::LuaListener(lua_State* L): super(getUniqueKey()), + mCoroName(LLCoros::getName()), mListener(new LLLeapListener( "LuaListener", [this](const std::string& pump, const LLSD& data) { return queueEvent(pump, data); })), // Listen for shutdown events on the "LLApp" LLEventPump. mShutdownConnection( - LLEventPumps::instance().obtain("LLApp").listen( + LLEventPumps::instance().obtain("LLLua").listen( LLEventPump::inventName("LuaState"), [this](const LLSD& status) { - const auto& statsd = status["status"]; - if (statsd.asString() != "running") + const auto& coro_name = status["coro"].asString(); + const auto& statsd = status["status"].asString(); + if ((statsd == "close_all") || ((statsd == "close") && (coro_name == mCoroName))) { // If a Lua script is still blocked in getNext() during // viewer shutdown, close the queue to wake up getNext(). diff --git a/indra/llcommon/lualistener.h b/indra/llcommon/lualistener.h index 40ccfba8fe..d349ee23fd 100644 --- a/indra/llcommon/lualistener.h +++ b/indra/llcommon/lualistener.h @@ -75,6 +75,8 @@ private: std::unique_ptr mListener; LLTempBoundListener mShutdownConnection; + + std::string mCoroName; }; #endif /* ! defined(LL_LUALISTENER_H) */ diff --git a/indra/newview/llfloaterluascripts.cpp b/indra/newview/llfloaterluascripts.cpp index bd845a97d6..8f6ccc50fa 100644 --- a/indra/newview/llfloaterluascripts.cpp +++ b/indra/newview/llfloaterluascripts.cpp @@ -45,6 +45,11 @@ LLFloaterLUAScripts::LLFloaterLUAScripts(const LLSD &key) { gViewerWindow->getWindow()->openFolder(mTargetFolderPath); }); + mCommitCallbackRegistrar.add("Script.Terminate", [this](LLUICtrl*, const LLSD &userdata) + { + LLEventPumps::instance().obtain("LLLua").post(llsd::map("status", "close", "coro", mCoroName)); + LLLUAmanager::terminateScript(mCoroName); + }); } @@ -113,6 +118,7 @@ void LLFloaterLUAScripts::onScrollListRightClicked(LLUICtrl *ctrl, S32 x, S32 y) if (menu) { mTargetFolderPath = std::filesystem::path((item->getColumn(1)->getValue().asString())).parent_path().string(); + mCoroName = item->getValue().asString(); menu->show(x, y); LLMenuGL::showPopup(this, menu, x, y); } diff --git a/indra/newview/llfloaterluascripts.h b/indra/newview/llfloaterluascripts.h index 548bbd10f6..c7c888b55a 100644 --- a/indra/newview/llfloaterluascripts.h +++ b/indra/newview/llfloaterluascripts.h @@ -47,6 +47,7 @@ private: std::unique_ptr mUpdateTimer; LLScrollListCtrl* mScriptList; std::string mTargetFolderPath; + std::string mCoroName; LLHandle mContextMenuHandle; }; diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 1d55313813..35a73aaabe 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -49,6 +49,7 @@ #include std::map LLLUAmanager::sScriptNames; +std::set LLLUAmanager::sTerminationList; lua_function(sleep, "sleep(seconds): pause the running coroutine") { @@ -188,6 +189,21 @@ void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn r // A script_finished_fn is used to initialize the LuaState. // It will be called when the LuaState is destroyed. LuaState L(finished_cb); + + static int index = 0; + lua_callbacks(L)->interrupt = [](lua_State *L, int gc) + { + if (gc >= 0) + return; + + std::set scripts = LLLUAmanager::getTerminationList(); + std::string coro = LLCoros::getName(); + if (scripts.find(coro) != scripts.end()) + { + sTerminationList.erase(coro); + lluau::error(L, "Script was terminated"); + } + }; std::string text{std::istreambuf_iterator(in_file), {}}; auto [count, result] = L.expr(filename, text); if (result_cb) diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h index fe4db22fca..d671719bc4 100644 --- a/indra/newview/llluamanager.h +++ b/indra/newview/llluamanager.h @@ -85,9 +85,12 @@ public: static void runScriptOnLogin(); static const std::map getScriptNames() { return sScriptNames; } + static std::set getTerminationList() { return sTerminationList; } + static void terminateScript(std::string& coro_name) { sTerminationList.insert(coro_name); } private: - static std::map sScriptNames; + static std::map sScriptNames; + static std::set sTerminationList; }; class LLRequireResolver diff --git a/indra/newview/skins/default/xui/en/menu_lua_scripts.xml b/indra/newview/skins/default/xui/en/menu_lua_scripts.xml index 8f718abe17..645fee405d 100644 --- a/indra/newview/skins/default/xui/en/menu_lua_scripts.xml +++ b/indra/newview/skins/default/xui/en/menu_lua_scripts.xml @@ -8,4 +8,12 @@ + + + + -- cgit v1.2.3 From 26ce33d8ead68c2dbcc37b2b1e040c072866fe5b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 9 Apr 2024 15:21:05 -0400 Subject: Add Lua Floater class to simplify Lua script showing floaters. Add test_luafloater_demo2.lua and test_luafloater_gesture_list2.lua examples. --- indra/newview/scripts/lua/Floater.lua | 151 +++++++++++++++++++++ indra/newview/scripts/lua/leap.lua | 7 +- .../newview/scripts/lua/test_luafloater_demo2.lua | 39 ++++++ .../scripts/lua/test_luafloater_gesture_list2.lua | 27 ++++ 4 files changed, 221 insertions(+), 3 deletions(-) create mode 100644 indra/newview/scripts/lua/Floater.lua create mode 100644 indra/newview/scripts/lua/test_luafloater_demo2.lua create mode 100644 indra/newview/scripts/lua/test_luafloater_gesture_list2.lua (limited to 'indra') diff --git a/indra/newview/scripts/lua/Floater.lua b/indra/newview/scripts/lua/Floater.lua new file mode 100644 index 0000000000..76efd47c43 --- /dev/null +++ b/indra/newview/scripts/lua/Floater.lua @@ -0,0 +1,151 @@ +-- Floater base class + +local leap = require 'leap' +local fiber = require 'fiber' + +-- list of all the events that a LLLuaFloater might send +local event_list = leap.request("LLFloaterReg", {op="getFloaterEvents"}).events +local event_set = {} +for _, event in pairs(event_list) do + event_set[event] = true +end + +local function _event(event_name) + if not event_set[event_name] then + error("Incorrect event name: " .. event_name, 3) + end + return event_name +end + +-- --------------------------------------------------------------------------- +local Floater = {} + +-- Pass: +-- relative file path to floater's XUI definition file +-- optional: sign up for additional events for defined control +-- {={action1, action2, ...}} +function Floater:new(path, extra) + local obj = setmetatable({}, self) + self.__index = self + + local path_parts = string.split(path, '/') + obj.name = 'Floater ' .. path_parts[#path_parts] + + obj._command = {op="showLuaFloater", xml_path=LL.abspath(path)} + if extra then + -- validate each of the actions for each specified control + for control, actions in pairs(extra) do + for _, action in pairs(actions) do + _event(action) + end + end + obj._command.extra_events = extra + end + + return obj +end + +function Floater:show() + local event = leap.request('LLFloaterReg', self._command) + self._pump = event.command_name + -- we use the returned reqid to claim subsequent unsolicited events + local reqid = event.reqid + + -- The response to 'showLuaFloater' *is* the 'post_build' event. Check if + -- subclass has a post_build() method. Honor the convention that if + -- handleEvents() returns false, we're done. + if not self:handleEvents(event) then + return + end + + local waitfor = leap.WaitFor:new(-1, self.name) + function waitfor:filter(pump, data) + if data.reqid == reqid then + return data + end + end + + fiber.launch( + self.name, + function () + event = waitfor:wait() + while event and self:handleEvents(event) do + event = waitfor:wait() + end + end) +end + +function Floater:post(action) + leap.send(self._pump, action) +end + +function Floater:request(action) + return leap.request(self._pump, action) +end + +-- local inspect = require 'inspect' + +function Floater:handleEvents(event_data) + local event = event_data.event + if event_set[event] == nil then + LL.print_warning(string.format('%s received unknown event %q', self.name, event)) + end + + -- Before checking for a general (e.g.) commit() method, first look for + -- commit_ctrl_name(): in other words, concatenate the event name with the + -- ctrl_name, with an underscore between. If there exists such a specific + -- method, call that. + local handler, ret + if event_data.ctrl_name then + local specific = event .. '_' .. event_data.ctrl_name + handler = self[specific] + if handler then + ret = handler(self, event_data) + -- Avoid 'return ret or true' because we explicitly want to allow + -- the handler to return false. + if ret ~= nil then + return ret + else + return true + end + end + end + + -- No specific "event_on_ctrl()" method found; try just "event()" + handler = self[event] + if handler then + ret = handler(self, event_data) + if ret ~= nil then + return ret + end +-- else +-- print(string.format('%s ignoring event %s', self.name, inspect(event_data))) + end + + -- We check for event() method before recognizing floater_close in case + -- the consumer needs to react specially to closing the floater. Now that + -- we've checked, recognize it ourselves. Returning false terminates the + -- anonymous fiber function launched by show(). + if event == _event('floater_close') then + LL.print_warning(self.name .. ' closed') + return false + end + return true +end + +-- onCtrl() permits a different dispatch style in which the general event() +-- method explicitly calls (e.g.) +-- self:onCtrl(event_data, { +-- ctrl_name=function() +-- self:post(...) +-- end, +-- ... +-- }) +function Floater:onCtrl(event_data, ctrl_map) + local handler = ctrl_map[event_data.ctrl_name] + if handler then + handler() + end +end + +return Floater diff --git a/indra/newview/scripts/lua/leap.lua b/indra/newview/scripts/lua/leap.lua index d19273e8bc..ade91789f0 100644 --- a/indra/newview/scripts/lua/leap.lua +++ b/indra/newview/scripts/lua/leap.lua @@ -183,14 +183,15 @@ function leap.generate(pump, data, checklast) -- bearing that reqid. Stamp the outbound request with that reqid, and -- send it. local reqid, waitfor = requestSetup(pump, data) - local ok, response + local ok, response, resumed_with repeat ok, response = pcall(waitfor.wait, waitfor) if not ok then break end - coroutine.yield(response) - until checklast and checklast(response) + -- can resume(false) to terminate generate() and clean up + resumed_with = coroutine.yield(response) + until (checklast and checklast(response)) or (resumed_with == false) -- If we break the above loop, whether or not due to error, clean up. pending[reqid] = nil if not ok then diff --git a/indra/newview/scripts/lua/test_luafloater_demo2.lua b/indra/newview/scripts/lua/test_luafloater_demo2.lua new file mode 100644 index 0000000000..9e24237d28 --- /dev/null +++ b/indra/newview/scripts/lua/test_luafloater_demo2.lua @@ -0,0 +1,39 @@ +local Floater = require 'Floater' +local leap = require 'leap' +local startup = require 'startup' + +local flt = Floater:new( + 'luafloater_demo.xml', + {show_time_lbl = {"right_mouse_down", "double_click"}}) + +-- override base-class handleEvents() to report the event data in the floater's display field +function flt:handleEvents(event_data) + self:post({action="add_text", ctrl_name="events_editor", value = event_data}) + -- forward the call to base-class handleEvents() + return Floater.handleEvents(self, event_data) +end + +function flt:commit_disable_ctrl(event_data) + self:post({action="set_enabled", ctrl_name="open_btn", value = (1 - event_data.value)}) +end + +function flt:commit_title_cmb(event_data) + self:post({action="set_title", value=event_data.value}) +end + +function flt:commit_open_btn(event_data) + floater_name = self:request({action="get_value", ctrl_name='openfloater_cmd'}).value + leap.send("LLFloaterReg", {name = floater_name, op = "showInstance"}) +end + +local function getCurrentTime() + local currentTime = os.date("*t") + return string.format("%02d:%02d:%02d", currentTime.hour, currentTime.min, currentTime.sec) +end + +function flt:double_click_show_time_lbl(event_data) + self:post({action="set_value", ctrl_name="time_lbl", value=getCurrentTime()}) +end + +startup.wait('STATE_LOGIN_WAIT') +flt:show() diff --git a/indra/newview/scripts/lua/test_luafloater_gesture_list2.lua b/indra/newview/scripts/lua/test_luafloater_gesture_list2.lua new file mode 100644 index 0000000000..d702d09c51 --- /dev/null +++ b/indra/newview/scripts/lua/test_luafloater_gesture_list2.lua @@ -0,0 +1,27 @@ +local Floater = require 'Floater' +local LLGesture = require 'LLGesture' +local startup = require 'startup' + +local flt = Floater:new( + "luafloater_gesture_list.xml", + {gesture_list = {"double_click"}}) + +function flt:post_build(event_data) + local gestures_uuid = LLGesture.getActiveGestures() + local action_data = {} + action_data.action = "add_list_element" + action_data.ctrl_name = "gesture_list" + local gestures = {} + for uuid, info in pairs(gestures_uuid) do + table.insert(gestures, {value = uuid, columns={column = "gesture_name", value = info.name}}) + end + action_data.value = gestures + self:post(action_data) +end + +function flt:double_click_gesture_list(event_data) + LLGesture.startGesture(event_data.value) +end + +startup.wait('STATE_STARTED') +flt:show() -- cgit v1.2.3 From e6aa6d22a107161b0bf08a45e9a90e749a824d9b Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Tue, 9 Apr 2024 22:37:34 +0300 Subject: mac build fix - remove unused variable --- indra/newview/llluamanager.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 35a73aaabe..344ab1bb99 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -190,7 +190,6 @@ void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn r // It will be called when the LuaState is destroyed. LuaState L(finished_cb); - static int index = 0; lua_callbacks(L)->interrupt = [](lua_State *L, int gc) { if (gc >= 0) -- cgit v1.2.3 From 396ae60e1528515d0cbdfc4290b24ccb172998c8 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 12 Apr 2024 19:28:29 +0300 Subject: 'Lua Scripts' floater clean up --- indra/llcommon/lualistener.cpp | 4 ++-- indra/newview/llfloaterluascripts.cpp | 16 +++++++++++----- indra/newview/llfloaterluascripts.h | 2 -- indra/newview/llluamanager.cpp | 8 ++++---- 4 files changed, 17 insertions(+), 13 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lualistener.cpp b/indra/llcommon/lualistener.cpp index c11ab6b1c3..d4bd73a9fb 100644 --- a/indra/llcommon/lualistener.cpp +++ b/indra/llcommon/lualistener.cpp @@ -44,8 +44,8 @@ LuaListener::LuaListener(lua_State* L): LLEventPump::inventName("LuaState"), [this](const LLSD& status) { - const auto& coro_name = status["coro"].asString(); - const auto& statsd = status["status"].asString(); + auto coro_name = status["coro"].asString(); + auto statsd = status["status"].asString(); if ((statsd == "close_all") || ((statsd == "close") && (coro_name == mCoroName))) { // If a Lua script is still blocked in getNext() during diff --git a/indra/newview/llfloaterluascripts.cpp b/indra/newview/llfloaterluascripts.cpp index 8f6ccc50fa..30353a7210 100644 --- a/indra/newview/llfloaterluascripts.cpp +++ b/indra/newview/llfloaterluascripts.cpp @@ -43,12 +43,20 @@ LLFloaterLUAScripts::LLFloaterLUAScripts(const LLSD &key) { mCommitCallbackRegistrar.add("Script.OpenFolder", [this](LLUICtrl*, const LLSD &userdata) { - gViewerWindow->getWindow()->openFolder(mTargetFolderPath); + if (mScriptList->hasSelectedItem()) + { + std::string target_folder_path = std::filesystem::path((mScriptList->getFirstSelected()->getColumn(1)->getValue().asString())).parent_path().string(); + gViewerWindow->getWindow()->openFolder(target_folder_path); + } }); mCommitCallbackRegistrar.add("Script.Terminate", [this](LLUICtrl*, const LLSD &userdata) { - LLEventPumps::instance().obtain("LLLua").post(llsd::map("status", "close", "coro", mCoroName)); - LLLUAmanager::terminateScript(mCoroName); + if (mScriptList->hasSelectedItem()) + { + std::string coro_name = mScriptList->getSelectedValue(); + LLEventPumps::instance().obtain("LLLua").post(llsd::map("status", "close", "coro", coro_name)); + LLLUAmanager::terminateScript(coro_name); + } }); } @@ -117,8 +125,6 @@ void LLFloaterLUAScripts::onScrollListRightClicked(LLUICtrl *ctrl, S32 x, S32 y) auto menu = mContextMenuHandle.get(); if (menu) { - mTargetFolderPath = std::filesystem::path((item->getColumn(1)->getValue().asString())).parent_path().string(); - mCoroName = item->getValue().asString(); menu->show(x, y); LLMenuGL::showPopup(this, menu, x, y); } diff --git a/indra/newview/llfloaterluascripts.h b/indra/newview/llfloaterluascripts.h index c7c888b55a..932c5c78dd 100644 --- a/indra/newview/llfloaterluascripts.h +++ b/indra/newview/llfloaterluascripts.h @@ -46,8 +46,6 @@ private: std::unique_ptr mUpdateTimer; LLScrollListCtrl* mScriptList; - std::string mTargetFolderPath; - std::string mCoroName; LLHandle mContextMenuHandle; }; diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 344ab1bb99..853ed5a634 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -192,14 +192,14 @@ void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn r lua_callbacks(L)->interrupt = [](lua_State *L, int gc) { + // skip if we're interrupting only for garbage collection if (gc >= 0) return; - std::set scripts = LLLUAmanager::getTerminationList(); - std::string coro = LLCoros::getName(); - if (scripts.find(coro) != scripts.end()) + auto it = sTerminationList.find(LLCoros::getName()); + if (it != sTerminationList.end()) { - sTerminationList.erase(coro); + sTerminationList.erase(it); lluau::error(L, "Script was terminated"); } }; -- cgit v1.2.3 From 44e3b1859a925c3d5c9a9caa841669fad9d322b4 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Tue, 16 Apr 2024 19:56:46 +0300 Subject: Call suspend() periodically to avoid viewer freeze --- indra/newview/llfloaterluadebug.cpp | 6 ++++-- indra/newview/llluamanager.cpp | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/newview/llfloaterluadebug.cpp b/indra/newview/llfloaterluadebug.cpp index f247528231..6fd11dd1c7 100644 --- a/indra/newview/llfloaterluadebug.cpp +++ b/indra/newview/llfloaterluadebug.cpp @@ -123,8 +123,10 @@ void LLFloaterLUADebug::completion(int count, const LLSD& result) if (count < 0) { // error: show error message - mResultOutput->insertText("*** "); - mResultOutput->pasteTextWithLinebreaks(result.asString()); + LLStyle::Params params; + params.readonly_color = LLUIColorTable::instance().getColor("LtRed"); + mResultOutput->appendText(result.asString(), false, params); + mResultOutput->endOfDoc(); return; } if (count == 1) diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 853ed5a634..dfa27ebc37 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -51,11 +51,22 @@ std::map LLLUAmanager::sScriptNames; std::set LLLUAmanager::sTerminationList; +const S32 INTERRUPTS_MAX_LIMIT = 20000; +const S32 INTERRUPTS_SUSPEND_LIMIT = 100; + +void set_interrupts_counter(lua_State *L, S32 counter) +{ + lua_pushstring(L, "_INTERRUPTS"); + lua_pushinteger(L, counter); + lua_rawset(L, LUA_REGISTRYINDEX); +} + lua_function(sleep, "sleep(seconds): pause the running coroutine") { F32 seconds = lua_tonumber(L, -1); lua_pop(L, 1); llcoro::suspendUntilTimeout(seconds); + set_interrupts_counter(L, 0); return 0; }; @@ -98,6 +109,8 @@ std::string lua_print_msg(lua_State* L, const std::string_view& level) std::string msg{ out.str() }; // put message out there for any interested party (*koff* LLFloaterLUADebug *koff*) LLEventPumps::instance().obtain("lua output").post(stringize(level, ": ", msg)); + + llcoro::suspend(); return msg; } @@ -155,6 +168,7 @@ lua_function(get_event_next, const auto& [pump, data]{ listener->getNext() }; lua_pushstdstring(L, pump); lua_pushllsd(L, data); + set_interrupts_counter(L, 0); return 2; } @@ -175,6 +189,25 @@ std::pair LLLUAmanager::waitScriptFile(const std::string& filename) return startScriptFile(filename).get(); } +void check_interrupts_counter(lua_State* L) +{ + lua_pushstring(L, "_INTERRUPTS"); + lua_rawget(L, LUA_REGISTRYINDEX); + S32 counter = lua_tointeger(L, -1); + lua_pop(L, 1); + + counter++; + if (counter > INTERRUPTS_MAX_LIMIT) + { + lluau::error(L, "Possible infinite loop, terminated."); + } + else if (counter % INTERRUPTS_SUSPEND_LIMIT == 0) + { + llcoro::suspend(); + } + set_interrupts_counter(L, counter); +} + void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn result_cb, script_finished_fn finished_cb) { // A script_result_fn will be called when LuaState::expr() completes. @@ -189,6 +222,7 @@ void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn r // A script_finished_fn is used to initialize the LuaState. // It will be called when the LuaState is destroyed. LuaState L(finished_cb); + set_interrupts_counter(L, 0); lua_callbacks(L)->interrupt = [](lua_State *L, int gc) { @@ -202,6 +236,7 @@ void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn r sTerminationList.erase(it); lluau::error(L, "Script was terminated"); } + check_interrupts_counter(L); }; std::string text{std::istreambuf_iterator(in_file), {}}; auto [count, result] = L.expr(filename, text); -- cgit v1.2.3 From e96f2926544e7f56eec0cc0ce66ea9e3e1e80eb9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 17 Apr 2024 14:49:31 -0400 Subject: Fix broken llimageworker_test.cpp. Please run integration tests before pushing to GitHub! --- indra/llimage/tests/llimageworker_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llimage/tests/llimageworker_test.cpp b/indra/llimage/tests/llimageworker_test.cpp index 0a97b739b0..ffcd7d257f 100644 --- a/indra/llimage/tests/llimageworker_test.cpp +++ b/indra/llimage/tests/llimageworker_test.cpp @@ -98,7 +98,7 @@ namespace tut done = res; *done = false; } - virtual void completed(bool success, LLImageRaw* raw, LLImageRaw* aux) + virtual void completed(bool success, LLImageRaw* raw, LLImageRaw* aux, U32) { *done = true; } -- cgit v1.2.3 From c78be38a6a4211f06876bc80b3f19f89a5f936e0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 17 Apr 2024 16:39:37 -0400 Subject: Reintroduce LLCoros::killreq() to request killing a named coroutine. Make LLCoros constructor echo "LLApp" status-change events on new "LLCoros" event pump. Rename LLCoros::kill() to killreq() because this operation only registers a request for the named coroutine to terminate next time it calls checkStop(). Add a new CoroData member to record the name of the coroutine requesting termination. killreq() sets that and also posts "killreq" to "LLCoros". Add an optional final-cleanup callback to LLCoros::checkStop(). Make checkStop() check for a pending killreq() request as well as viewer termination. Introduce new LLCoros::Killed exception for that case. Introduce LLCoros::getStopListener(), with two overloads, to encapsulate some of the messy logic to listen (perhaps temporarily) for viewer shutdown. Both overloads are for use by code at the source end of a queue or promise or other resource for which coroutines might still be waiting at viewer shutdown time. One overload is specifically for when the caller knows the name of the one and only coroutine that will wait on the resource (e.g. because the caller IS that coroutine). That overload honors killreq(). Use getStopListener() to simplify the four existing places where we set up such a listener. Add a fifth: also make WorkQueue listen for viewer shutdown (resolving a TODO comment). Remove LLLUAmanager::terminateScript(), getTerminationList() and the static sTerminationList. In the Lua interrupt callback, instead of checking sTerminationList, call LLCoros::checkStop(). Change LLFloaterLUAScripts terminate-script logic to call LLCoros::killreq() instead of posting on "LLLua" and calling LLLUAmanager::terminateScript(). Drop LLApp::setStatus() posting to "LLLua" LLEventPump: the above makes that moot. --- indra/llcommon/llapp.cpp | 1 - indra/llcommon/llcoros.cpp | 127 +++++++++++++++++++++++++------ indra/llcommon/llcoros.h | 67 +++++++++++----- indra/llcommon/lleventcoro.cpp | 43 +++++------ indra/llcommon/lualistener.cpp | 19 ++--- indra/llcommon/threadpool.cpp | 19 ++--- indra/llcommon/threadpool.h | 6 +- indra/llcommon/workqueue.cpp | 7 +- indra/llcommon/workqueue.h | 3 + indra/llmessage/llcoproceduremanager.cpp | 21 ++--- indra/newview/llfloaterluascripts.cpp | 3 +- indra/newview/llluamanager.cpp | 13 +--- indra/newview/llluamanager.h | 3 - 13 files changed, 209 insertions(+), 123 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index 6fe80c9954..90d0c28eb1 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -428,7 +428,6 @@ void LLApp::setStatus(EAppStatus status) statsd = LLSD::Integer(status); } LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", statsd)); - LLEventPumps::instance().obtain("LLLua").post(llsd::map("status", "close_all")); } } diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index c13900f74a..1926941d1f 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -51,18 +51,19 @@ #endif // other Linden headers #include "llapp.h" -#include "lltimer.h" -#include "llevents.h" #include "llerror.h" -#include "stringize.h" +#include "llevents.h" #include "llexception.h" +#include "llsdutil.h" +#include "lltimer.h" +#include "stringize.h" #if LL_WINDOWS #include #endif // static -LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller) +LLCoros::CoroData& LLCoros::get_CoroData(const std::string&) { CoroData* current{ nullptr }; // be careful about attempted accesses in the final throes of app shutdown @@ -94,7 +95,7 @@ LLCoros::coro::id LLCoros::get_self() //static void LLCoros::set_consuming(bool consuming) { - CoroData& data(get_CoroData("set_consuming()")); + auto& data(get_CoroData("set_consuming()")); // DO NOT call this on the main() coroutine. llassert_always(! data.mName.empty()); data.mConsuming = consuming; @@ -128,6 +129,15 @@ LLCoros::LLCoros(): // points to it. So initialize it with a no-op deleter. mCurrent{ [](CoroData*){} } { + auto& llapp{ LLEventPumps::instance().obtain("LLApp") }; + if (llapp.getListener("LLCoros") == LLBoundListener()) + { + // chain our "LLCoros" pump onto "LLApp" pump: echo events posted to "LLApp" + mConn = llapp.listen( + "LLCoros", + [](const LLSD& event) + { return LLEventPumps::instance().obtain("LLCoros").post(event); }); + } } LLCoros::~LLCoros() @@ -177,26 +187,26 @@ std::string LLCoros::generateDistinctName(const std::string& prefix) const // Until we find an unused name, append a numeric suffix for uniqueness. while (CoroData::getInstance(name)) { - name = STRINGIZE(prefix << unique++); + name = stringize(prefix, unique++); } return name; } -/*==========================================================================*| -bool LLCoros::kill(const std::string& name) +bool LLCoros::killreq(const std::string& name) { - CoroMap::iterator found = mCoros.find(name); - if (found == mCoros.end()) + auto found = CoroData::getInstance(name); + if (! found) { return false; } - // Because this is a boost::ptr_map, erasing the map entry also destroys - // the referenced heap object, in this case the boost::coroutine object, - // which will terminate the coroutine. - mCoros.erase(found); + // Next time the subject coroutine calls checkStop(), make it terminate. + found->mKilledBy = getName(); + // But if it's waiting for something, notify anyone in a position to poke + // it. + LLEventPumps::instance().obtain("LLCoros").post( + llsd::map("status", "killreq", "coro", name)); return true; } -|*==========================================================================*/ //static std::string LLCoros::getName() @@ -207,7 +217,7 @@ std::string LLCoros::getName() //static std::string LLCoros::logname() { - LLCoros::CoroData& data(get_CoroData("logname()")); + auto& data(get_CoroData("logname()")); return data.mName.empty()? data.getKey() : data.mName; } @@ -360,7 +370,7 @@ void LLCoros::toplevel(std::string name, callable_t callable) // Any uncaught exception derived from LLContinueError will be caught // here and logged. This coroutine will terminate but the rest of the // viewer will carry on. - LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << name)); + LOG_UNHANDLED_EXCEPTION(stringize("coroutine ", name)); } catch (...) { @@ -373,15 +383,24 @@ void LLCoros::toplevel(std::string name, callable_t callable) } //static -void LLCoros::checkStop() +void LLCoros::checkStop(callable_t cleanup) { + // don't replicate this 'if' test throughout the code below + if (! cleanup) + { + cleanup = {[](){}}; // hey, look, I'm coding in Haskell! + } + if (wasDeleted()) { + cleanup(); LLTHROW(Shutdown("LLCoros was deleted")); } - // do this AFTER the check above, because getName() depends on - // get_CoroData(), which depends on the local_ptr in our instance(). - if (getName().empty()) + + // do this AFTER the check above, because get_CoroData() depends on the + // local_ptr in our instance(). + auto& data(get_CoroData("checkStop()")); + if (data.mName.empty()) { // Our Stop exception and its subclasses are intended to stop loitering // coroutines. Don't throw it from the main coroutine. @@ -389,19 +408,80 @@ void LLCoros::checkStop() } if (LLApp::isStopped()) { + cleanup(); LLTHROW(Stopped("viewer is stopped")); } if (! LLApp::isRunning()) { + cleanup(); LLTHROW(Stopping("viewer is stopping")); } + if (! data.mKilledBy.empty()) + { + // Someone wants to kill this coroutine + cleanup(); + LLTHROW(Killed(stringize("coroutine ", data.mName, " killed by ", data.mKilledBy))); + } +} + +LLBoundListener LLCoros::getStopListener(const std::string& caller, LLVoidListener cleanup) +{ + if (! cleanup) + return {}; + + // This overload only responds to viewer shutdown. + return LLEventPumps::instance().obtain("LLCoros") + .listen( + LLEventPump::inventName(caller), + [cleanup](const LLSD& event) + { + auto status{ event["status"].asString() }; + if (status != "running" && status != "killreq") + { + cleanup(event); + } + return false; + }); +} + +LLBoundListener LLCoros::getStopListener(const std::string& caller, + const std::string& cnsmr, + LLVoidListener cleanup) +{ + if (! cleanup) + return {}; + + std::string consumer{cnsmr}; + if (consumer.empty()) + { + consumer = getName(); + } + + // This overload responds to viewer shutdown and to killreq(consumer). + return LLEventPumps::instance().obtain("LLCoros") + .listen( + LLEventPump::inventName(caller), + [consumer, cleanup](const LLSD& event) + { + auto status{ event["status"].asString() }; + if (status == "killreq") + { + if (event["coro"].asString() == consumer) + { + cleanup(event); + } + } + else if (status != "running") + { + cleanup(event); + } + return false; + }); } LLCoros::CoroData::CoroData(const std::string& name): LLInstanceTracker(name), mName(name), - // don't consume events unless specifically directed - mConsuming(false), mCreationTime(LLTimer::getTotalSeconds()) { } @@ -414,7 +494,6 @@ LLCoros::CoroData::CoroData(int n): // empty string as its visible name because some consumers test for that. LLInstanceTracker("main" + stringize(n)), mName(), - mConsuming(false), mCreationTime(LLTimer::getTotalSeconds()) { } diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h index fd878f20ad..61c0fef1c3 100644 --- a/indra/llcommon/llcoros.h +++ b/indra/llcommon/llcoros.h @@ -29,17 +29,18 @@ #if ! defined(LL_LLCOROS_H) #define LL_LLCOROS_H +#include "llevents.h" #include "llexception.h" +#include "llinstancetracker.h" +#include "llsingleton.h" +#include "mutex.h" #include #include #include -#include "mutex.h" -#include "llsingleton.h" -#include "llinstancetracker.h" -#include -#include #include +#include #include +#include // e.g. #include LLCOROS_MUTEX_HEADER #define LLCOROS_MUTEX_HEADER @@ -101,7 +102,7 @@ public: /// stuck with the term "coroutine." typedef boost::fibers::fiber coro; /// Canonical callable type - typedef boost::function callable_t; + typedef std::function callable_t; /** * Create and start running a new coroutine with specified name. The name @@ -143,13 +144,13 @@ public: std::string launch(const std::string& prefix, const callable_t& callable); /** - * Abort a running coroutine by name. Normally, when a coroutine either + * Ask the named coroutine to abort. Normally, when a coroutine either * runs to completion or terminates with an exception, LLCoros quietly * cleans it up. This is for use only when you must explicitly interrupt * one prematurely. Returns @c true if the specified name was found and * still running at the time. */ -// bool kill(const std::string& name); + bool killreq(const std::string& name); /** * From within a coroutine, look up the (tweaked) name string by which @@ -251,15 +252,21 @@ public: /// thrown by checkStop() // It may sound ironic that Stop is derived from LLContinueError, but the // point is that LLContinueError is the category of exception that should - // not immediately crash the viewer. Stop and its subclasses are to notify - // coroutines that the viewer intends to shut down. The expected response - // is to terminate the coroutine, rather than abort the viewer. + // not immediately crash the viewer. Stop and its subclasses are to tell + // coroutines to terminate, e.g. because the viewer is shutting down. We + // do not want any such exception to crash the viewer. struct Stop: public LLContinueError { Stop(const std::string& what): LLContinueError(what) {} }; - /// early stages + /// someone wants to kill this specific coroutine + struct Killed: public Stop + { + Killed(const std::string& what): Stop(what) {} + }; + + /// early shutdown stages struct Stopping: public Stop { Stopping(const std::string& what): Stop(what) {} @@ -278,9 +285,31 @@ public: }; /// Call this intermittently if there's a chance your coroutine might - /// continue running into application shutdown. Throws Stop if LLCoros has - /// been cleaned up. - static void checkStop(); + /// still be running at application shutdown. Throws one of the Stop + /// subclasses if the caller needs to terminate. Pass a cleanup function + /// if you need to execute that cleanup before terminating. + /// Of course, if your cleanup function throws, that will be the exception + /// propagated by checkStop(). + static void checkStop(callable_t cleanup={}); + + /// Call getStopListener() at the source end of a queue, promise or other + /// resource on which coroutines will wait, so that shutdown can wake up + /// consuming coroutines. @a caller should distinguish who's calling. The + /// passed @a cleanup function must close the queue, break the promise or + /// otherwise cause waiting consumers to wake up in an abnormal way. It's + /// advisable to store the returned LLBoundListener in an + /// LLTempBoundListener, or otherwise arrange to disconnect it. + static LLBoundListener getStopListener(const std::string& caller, LLVoidListener cleanup); + + /// This getStopListener() overload is like the two-argument one, for use + /// when we know the name of the only coroutine that will wait on the + /// resource in question. Pass @a consumer as the empty string if the + /// consumer coroutine is the same as the calling coroutine. Unlike the + /// two-argument getStopListener(), this one also responds to + /// killreq(target). + static LLBoundListener getStopListener(const std::string& caller, + const std::string& consumer, + LLVoidListener cleanup); /** * Aliases for promise and future. An older underlying future implementation @@ -312,6 +341,8 @@ private: static CoroData& get_CoroData(const std::string& caller); void saveException(const std::string& name, std::exception_ptr exc); + LLTempBoundListener mConn; + struct ExceptionData { ExceptionData(const std::string& nm, std::exception_ptr exc): @@ -335,8 +366,10 @@ private: // tweaked name of the current coroutine const std::string mName; - // set_consuming() state - bool mConsuming; + // set_consuming() state -- don't consume events unless specifically directed + bool mConsuming{ false }; + // killed by which coroutine + std::string mKilledBy; // setStatus() state std::string mStatus; F64 mCreationTime; // since epoch diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index 067b5e6fbc..d651aae39c 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -119,7 +119,7 @@ void llcoro::suspendUntilTimeout(float seconds) // We used to call boost::this_fiber::sleep_for(). But some coroutines // (e.g. LLExperienceCache::idleCoro()) sit in a suspendUntilTimeout() // loop, in which case a sleep_for() call risks sleeping through shutdown. - // So instead, listen for "LLApp" state-changing events -- which + // So instead, listen for LLApp state-changing events -- which // fortunately is handled for us by suspendUntilEventOnWithTimeout(). // Wait for an event on a bogus LLEventPump on which nobody ever posts // events. Don't make it static because that would force instantiation of @@ -132,8 +132,8 @@ void llcoro::suspendUntilTimeout(float seconds) // Timeout is the NORMAL case for this call! static LLSD timedout; // Deliver, but ignore, timedout when (as usual) we did not receive any - // "LLApp" event. The point is that suspendUntilEventOnWithTimeout() will - // itself throw Stopping when "LLApp" starts broadcasting shutdown events. + // LLApp event. The point is that suspendUntilEventOnWithTimeout() will + // itself throw Stopping when LLApp starts broadcasting shutdown events. suspendUntilEventOnWithTimeout(bogus, seconds, timedout); } @@ -167,33 +167,26 @@ postAndSuspendSetup(const std::string& callerName, // "LLApp" were an LLEventMailDrop. But if we ever go there, we'd want to // notice the pending LLApp status first. LLBoundListener stopper( - LLEventPumps::instance().obtain("LLApp").listen( + LLCoros::getStopListener( listenerName, + LLCoros::instance().getName(), [&promise, listenerName](const LLSD& status) { - // anything except "running" should wake up the waiting - // coroutine - auto& statsd = status["status"]; - if (statsd.asString() != "running") + LL_DEBUGS("lleventcoro") << listenerName + << " spotted status " << status + << ", throwing Stopping" << LL_ENDL; + try + { + promise.set_exception( + std::make_exception_ptr( + LLCoros::Stopping("status " + stringize(status)))); + } + catch (const boost::fibers::promise_already_satisfied&) { - LL_DEBUGS("lleventcoro") << listenerName - << " spotted status " << statsd - << ", throwing Stopping" << LL_ENDL; - try - { - promise.set_exception( - std::make_exception_ptr( - LLCoros::Stopping("status " + statsd.asString()))); - } - catch (const boost::fibers::promise_already_satisfied&) - { - LL_WARNS("lleventcoro") << listenerName - << " couldn't throw Stopping " - "because promise already set" << LL_ENDL; - } + LL_WARNS("lleventcoro") << listenerName + << " couldn't throw Stopping " + "because promise already set" << LL_ENDL; } - // do not consume -- every listener must see status - return false; })); LLBoundListener connection( replyPump.listen( diff --git a/indra/llcommon/lualistener.cpp b/indra/llcommon/lualistener.cpp index d4bd73a9fb..b7036c1c48 100644 --- a/indra/llcommon/lualistener.cpp +++ b/indra/llcommon/lualistener.cpp @@ -38,21 +38,16 @@ LuaListener::LuaListener(lua_State* L): "LuaListener", [this](const std::string& pump, const LLSD& data) { return queueEvent(pump, data); })), - // Listen for shutdown events on the "LLApp" LLEventPump. + // Listen for shutdown events. mShutdownConnection( - LLEventPumps::instance().obtain("LLLua").listen( + LLCoros::getStopListener( LLEventPump::inventName("LuaState"), - [this](const LLSD& status) + mCoroName, + [this](const LLSD&) { - auto coro_name = status["coro"].asString(); - auto statsd = status["status"].asString(); - if ((statsd == "close_all") || ((statsd == "close") && (coro_name == mCoroName))) - { - // If a Lua script is still blocked in getNext() during - // viewer shutdown, close the queue to wake up getNext(). - mQueue.close(); - } - return false; + // If a Lua script is still blocked in getNext() during + // viewer shutdown, close the queue to wake up getNext(). + mQueue.close(); })) {} diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp index c48989358e..edccdb097b 100644 --- a/indra/llcommon/threadpool.cpp +++ b/indra/llcommon/threadpool.cpp @@ -18,6 +18,7 @@ // external library headers // other Linden headers #include "commoncontrol.h" +#include "llcoros.h" #include "llerror.h" #include "llevents.h" #include "llsd.h" @@ -90,20 +91,14 @@ void LL::ThreadPoolBase::start() return; } - // Listen on "LLApp", and when the app is shutting down, close the queue - // and join the workers. - LLEventPumps::instance().obtain("LLApp").listen( + // When the app is shutting down, close the queue and join the workers. + mStopListener = LLCoros::getStopListener( mName, - [this](const LLSD& stat) + [this](const LLSD& status) { - std::string status(stat["status"]); - if (status != "running") - { - // viewer is starting shutdown -- proclaim the end is nigh! - LL_DEBUGS("ThreadPool") << mName << " saw " << status << LL_ENDL; - close(); - } - return false; + // viewer is starting shutdown -- proclaim the end is nigh! + LL_DEBUGS("ThreadPool") << mName << " saw " << status << LL_ENDL; + close(); }); } diff --git a/indra/llcommon/threadpool.h b/indra/llcommon/threadpool.h index 74056aea17..7ced7fbf9f 100644 --- a/indra/llcommon/threadpool.h +++ b/indra/llcommon/threadpool.h @@ -13,6 +13,7 @@ #if ! defined(LL_THREADPOOL_H) #define LL_THREADPOOL_H +#include "llcoros.h" #include "threadpool_fwd.h" #include "workqueue.h" #include // std::unique_ptr @@ -52,8 +53,8 @@ namespace LL void start(); /** - * ThreadPool listens for application shutdown messages on the "LLApp" - * LLEventPump. Call close() to shut down this ThreadPool early. + * ThreadPool listens for application shutdown events. Call close() to + * shut down this ThreadPool early. */ virtual void close(); @@ -95,6 +96,7 @@ namespace LL std::string mName; size_t mThreadCount; + LLTempBoundListener mStopListener; }; /** diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp index cf80ce0656..800547084a 100644 --- a/indra/llcommon/workqueue.cpp +++ b/indra/llcommon/workqueue.cpp @@ -32,8 +32,11 @@ using Lock = LLCoros::LockType; LL::WorkQueueBase::WorkQueueBase(const std::string& name): super(makeName(name)) { - // TODO: register for "LLApp" events so we can implicitly close() on - // viewer shutdown. + // Register for status change events so we'll implicitly close() on viewer + // shutdown. + mStopListener = LLCoros::getStopListener( + "WorkQueue:" + getKey(), + [this](const LLSD&){ close(); }); } void LL::WorkQueueBase::runUntilClose() diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h index ec0700a718..4c46290f2a 100644 --- a/indra/llcommon/workqueue.h +++ b/indra/llcommon/workqueue.h @@ -13,6 +13,7 @@ #define LL_WORKQUEUE_H #include "llcoros.h" +#include "llevents.h" #include "llexception.h" #include "llinstancetracker.h" #include "llinstancetrackersubclass.h" @@ -194,6 +195,8 @@ namespace LL static std::string makeName(const std::string& name); void callWork(const Work& work); + LLTempBoundListener mStopListener; + private: virtual Work pop_() = 0; virtual bool tryPop_(Work&) = 0; diff --git a/indra/llmessage/llcoproceduremanager.cpp b/indra/llmessage/llcoproceduremanager.cpp index ebbaea9b12..ad0e0178b6 100644 --- a/indra/llmessage/llcoproceduremanager.cpp +++ b/indra/llmessage/llcoproceduremanager.cpp @@ -307,25 +307,20 @@ LLCoprocedurePool::LLCoprocedurePool(const std::string &poolName, size_t size): { try { - // store in our LLTempBoundListener so that when the LLCoprocedurePool is - // destroyed, we implicitly disconnect from this LLEventPump - // Monitores application status - mStatusListener = LLEventPumps::instance().obtain("LLApp").listen( + // Store in our LLTempBoundListener so that when the LLCoprocedurePool is + // destroyed, we implicitly disconnect from this LLEventPump. + // Monitors application status. + mStatusListener = LLCoros::getStopListener( poolName + "_pool", // Make sure it won't repeat names from lleventcoro - [pendingCoprocs = mPendingCoprocs, poolName](const LLSD& status) - { - auto& statsd = status["status"]; - if (statsd.asString() != "running") + [pendingCoprocs = mPendingCoprocs, poolName](const LLSD& event) { LL_INFOS("CoProcMgr") << "Pool " << poolName - << " closing queue because status " << statsd + << " closing queue because status " << event << LL_ENDL; // This should ensure that all waiting coprocedures in this // pool will wake up and terminate. pendingCoprocs->close(); - } - return false; - }); + }); } catch (const LLEventPump::DupListenerName &) { @@ -334,7 +329,7 @@ LLCoprocedurePool::LLCoprocedurePool(const std::string &poolName, size_t size): // // If this somehow happens again it is better to crash later on shutdown due to pump // not stopping coroutine and see warning in logs than on startup or during login. - LL_WARNS("CoProcMgr") << "Attempted to register dupplicate listener name: " << poolName + LL_WARNS("CoProcMgr") << "Attempted to register duplicate listener name: " << poolName << "_pool. Failed to start listener." << LL_ENDL; llassert(0); // Fix Me! Ignoring missing listener! diff --git a/indra/newview/llfloaterluascripts.cpp b/indra/newview/llfloaterluascripts.cpp index 30353a7210..39d5816b0d 100644 --- a/indra/newview/llfloaterluascripts.cpp +++ b/indra/newview/llfloaterluascripts.cpp @@ -54,8 +54,7 @@ LLFloaterLUAScripts::LLFloaterLUAScripts(const LLSD &key) if (mScriptList->hasSelectedItem()) { std::string coro_name = mScriptList->getSelectedValue(); - LLEventPumps::instance().obtain("LLLua").post(llsd::map("status", "close", "coro", coro_name)); - LLLUAmanager::terminateScript(coro_name); + LLCoros::instance().killreq(coro_name); } }); } diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index dfa27ebc37..e2950648a6 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -49,7 +49,6 @@ #include std::map LLLUAmanager::sScriptNames; -std::set LLLUAmanager::sTerminationList; const S32 INTERRUPTS_MAX_LIMIT = 20000; const S32 INTERRUPTS_SUSPEND_LIMIT = 100; @@ -196,7 +195,7 @@ void check_interrupts_counter(lua_State* L) S32 counter = lua_tointeger(L, -1); lua_pop(L, 1); - counter++; + set_interrupts_counter(L, ++counter); if (counter > INTERRUPTS_MAX_LIMIT) { lluau::error(L, "Possible infinite loop, terminated."); @@ -205,7 +204,6 @@ void check_interrupts_counter(lua_State* L) { llcoro::suspend(); } - set_interrupts_counter(L, counter); } void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn result_cb, script_finished_fn finished_cb) @@ -229,13 +227,8 @@ void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn r // skip if we're interrupting only for garbage collection if (gc >= 0) return; - - auto it = sTerminationList.find(LLCoros::getName()); - if (it != sTerminationList.end()) - { - sTerminationList.erase(it); - lluau::error(L, "Script was terminated"); - } + + LLCoros::checkStop(); check_interrupts_counter(L); }; std::string text{std::istreambuf_iterator(in_file), {}}; diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h index d671719bc4..af9dcf70c2 100644 --- a/indra/newview/llluamanager.h +++ b/indra/newview/llluamanager.h @@ -85,12 +85,9 @@ public: static void runScriptOnLogin(); static const std::map getScriptNames() { return sScriptNames; } - static std::set getTerminationList() { return sTerminationList; } - static void terminateScript(std::string& coro_name) { sTerminationList.insert(coro_name); } private: static std::map sScriptNames; - static std::set sTerminationList; }; class LLRequireResolver -- cgit v1.2.3 From 427f971c200e15197f2cd8a9136377cac6f72e77 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 18 Apr 2024 11:46:04 -0400 Subject: Fix LuaListener member initialization order. --- indra/llcommon/lualistener.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lualistener.h b/indra/llcommon/lualistener.h index d349ee23fd..85fb093cd6 100644 --- a/indra/llcommon/lualistener.h +++ b/indra/llcommon/lualistener.h @@ -73,10 +73,9 @@ private: LLThreadSafeQueue mQueue; + std::string mCoroName; std::unique_ptr mListener; LLTempBoundListener mShutdownConnection; - - std::string mCoroName; }; #endif /* ! defined(LL_LUALISTENER_H) */ -- cgit v1.2.3 From dba3b3b10c317eef14cbc24e519a8e4620bfc6e8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 18 Apr 2024 11:51:50 -0400 Subject: In debug.h, add debug_expr() macro for inline expressions. Break out LOGTEST_enabled() inline function because it's used for both Debug and debug_expr(). --- indra/test/debug.h | 48 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 13 deletions(-) (limited to 'indra') diff --git a/indra/test/debug.h b/indra/test/debug.h index 3c4f3cabb4..2f6a114761 100644 --- a/indra/test/debug.h +++ b/indra/test/debug.h @@ -37,29 +37,37 @@ * Debugging stuff *****************************************************************************/ /** - * This class is intended to illuminate entry to a given block, exit from the - * same block and checkpoints along the way. It also provides a convenient - * place to turn std::cerr output on and off. - * - * If the environment variable LOGTEST is non-empty, each Debug instance will - * announce its construction and destruction, presumably at entry and exit to - * the block in which it's declared. Moreover, any arguments passed to its - * operator()() will be streamed to std::cerr, prefixed by the block - * description. + * Return true if the environment variable LOGTEST is non-empty. * * The variable LOGTEST is used because that's the environment variable * checked by test.cpp, our TUT main() program, to turn on LLError logging. It * is expected that Debug is solely for use in test programs. */ +inline +bool LOGTEST_enabled() +{ + auto LOGTEST{ getenv("LOGTEST") }; + // debug output enabled when LOGTEST is set AND non-empty + return LOGTEST && *LOGTEST; +} + +/** + * This class is intended to illuminate entry to a given block, exit from the + * same block and checkpoints along the way. It also provides a convenient + * place to turn std::cerr output on and off. + * + * If enabled, each Debug instance will announce its construction and + * destruction, presumably at entry and exit to the block in which it's + * declared. Moreover, any arguments passed to its operator()() will be + * streamed to std::cerr, prefixed by the block description. + */ class Debug { public: template Debug(ARGS&&... args): mBlock(stringize(std::forward(args)...)), - mLOGTEST(getenv("LOGTEST")), - // debug output enabled when LOGTEST is set AND non-empty - mEnabled(mLOGTEST && *mLOGTEST) + mEnabled(LOGTEST_enabled()) { (*this)("entry"); } @@ -85,7 +93,6 @@ public: private: const std::string mBlock; - const char* mLOGTEST; bool mEnabled; }; @@ -93,4 +100,19 @@ private: // of the Debug block. #define DEBUG Debug debug(LL_PRETTY_FUNCTION) +/// If enabled, debug_expr(expression) gives you output concerning an inline +/// expression such as a class member initializer. +#define debug_expr(expr) debug_expr_(#expr, [&](){ return expr; }) + +template +inline auto debug_expr_(const char* strexpr, EXPR&& lambda) +{ + if (! LOGTEST_enabled()) + return std::forward(lambda)(); + print("Before: ", strexpr); + auto result{ std::forward(lambda)() }; + print(strexpr, " -> ", result); + return result; +} + #endif /* ! defined(LL_DEBUG_H) */ -- cgit v1.2.3 From c05bf5acd487e78e414e3d679f6ec5dbfaa4169d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 18 Apr 2024 16:15:35 -0400 Subject: Remove redundant LLEventPump::inventName() call. --- indra/llcommon/lualistener.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/lualistener.cpp b/indra/llcommon/lualistener.cpp index b7036c1c48..5c4989e891 100644 --- a/indra/llcommon/lualistener.cpp +++ b/indra/llcommon/lualistener.cpp @@ -41,7 +41,7 @@ LuaListener::LuaListener(lua_State* L): // Listen for shutdown events. mShutdownConnection( LLCoros::getStopListener( - LLEventPump::inventName("LuaState"), + "LuaState", mCoroName, [this](const LLSD&) { -- cgit v1.2.3 From fa821576c010eca2cacb0fab25dd240de4062b31 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 18 Apr 2024 16:34:32 -0400 Subject: Move {set,check}_interrupts_counter() to lluau namespace. Use in LuaState::expr() so we can catch a runaway in-memory Lua chunk as well as a script read from a file. --- indra/llcommon/lua_function.cpp | 44 +++++++++++++++++++++++++++++++++++++++++ indra/llcommon/lua_function.h | 3 +++ indra/newview/llluamanager.cpp | 43 ++-------------------------------------- 3 files changed, 49 insertions(+), 41 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 332a08a444..08bc65e0c5 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -31,6 +31,9 @@ #include "lualistener.h" #include "stringize.h" +const S32 INTERRUPTS_MAX_LIMIT = 20000; +const S32 INTERRUPTS_SUSPEND_LIMIT = 100; + #define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n))) #define lua_rawlen lua_objlen @@ -77,6 +80,35 @@ fsyspath lluau::source_path(lua_State* L) return ar.source; } +void lluau::set_interrupts_counter(lua_State *L, S32 counter) +{ + luaL_checkstack(L, 2, nullptr); + lua_pushstring(L, "_INTERRUPTS"); + lua_pushinteger(L, counter); + lua_rawset(L, LUA_REGISTRYINDEX); +} + +void lluau::check_interrupts_counter(lua_State* L) +{ + luaL_checkstack(L, 1, nullptr); + lua_pushstring(L, "_INTERRUPTS"); + lua_rawget(L, LUA_REGISTRYINDEX); + S32 counter = lua_tointeger(L, -1); + lua_pop(L, 1); + + lluau::set_interrupts_counter(L, ++counter); + if (counter > INTERRUPTS_MAX_LIMIT) + { + lluau::error(L, "Possible infinite loop, terminated."); + } + else if (counter % INTERRUPTS_SUSPEND_LIMIT == 0) + { + LL_DEBUGS("Lua") << LLCoros::getName() << " suspending at " << counter << " interrupts" + << LL_ENDL; + llcoro::suspend(); + } +} + /***************************************************************************** * Lua <=> C++ conversions *****************************************************************************/ @@ -485,6 +517,18 @@ bool LuaState::checkLua(const std::string& desc, int r) std::pair LuaState::expr(const std::string& desc, const std::string& text) { + lluau::set_interrupts_counter(mState, 0); + + lua_callbacks(mState)->interrupt = [](lua_State *L, int gc) + { + // skip if we're interrupting only for garbage collection + if (gc >= 0) + return; + + LLCoros::checkStop(); + lluau::check_interrupts_counter(L); + }; + if (! checkLua(desc, lluau::dostring(mState, desc, text))) { LL_WARNS("Lua") << desc << " error: " << mError << LL_ENDL; diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index ec1e6cdb10..e7013f92c6 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -52,6 +52,9 @@ namespace lluau int loadstring(lua_State* L, const std::string& desc, const std::string& text); fsyspath source_path(lua_State* L); + + void set_interrupts_counter(lua_State *L, S32 counter); + void check_interrupts_counter(lua_State* L); } // namespace lluau std::string lua_tostdstring(lua_State* L, int index); diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index e2950648a6..97779a12ad 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -50,22 +50,12 @@ std::map LLLUAmanager::sScriptNames; -const S32 INTERRUPTS_MAX_LIMIT = 20000; -const S32 INTERRUPTS_SUSPEND_LIMIT = 100; - -void set_interrupts_counter(lua_State *L, S32 counter) -{ - lua_pushstring(L, "_INTERRUPTS"); - lua_pushinteger(L, counter); - lua_rawset(L, LUA_REGISTRYINDEX); -} - lua_function(sleep, "sleep(seconds): pause the running coroutine") { F32 seconds = lua_tonumber(L, -1); lua_pop(L, 1); llcoro::suspendUntilTimeout(seconds); - set_interrupts_counter(L, 0); + lluau::set_interrupts_counter(L, 0); return 0; }; @@ -167,7 +157,7 @@ lua_function(get_event_next, const auto& [pump, data]{ listener->getNext() }; lua_pushstdstring(L, pump); lua_pushllsd(L, data); - set_interrupts_counter(L, 0); + lluau::set_interrupts_counter(L, 0); return 2; } @@ -188,24 +178,6 @@ std::pair LLLUAmanager::waitScriptFile(const std::string& filename) return startScriptFile(filename).get(); } -void check_interrupts_counter(lua_State* L) -{ - lua_pushstring(L, "_INTERRUPTS"); - lua_rawget(L, LUA_REGISTRYINDEX); - S32 counter = lua_tointeger(L, -1); - lua_pop(L, 1); - - set_interrupts_counter(L, ++counter); - if (counter > INTERRUPTS_MAX_LIMIT) - { - lluau::error(L, "Possible infinite loop, terminated."); - } - else if (counter % INTERRUPTS_SUSPEND_LIMIT == 0) - { - llcoro::suspend(); - } -} - void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn result_cb, script_finished_fn finished_cb) { // A script_result_fn will be called when LuaState::expr() completes. @@ -220,17 +192,6 @@ void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn r // A script_finished_fn is used to initialize the LuaState. // It will be called when the LuaState is destroyed. LuaState L(finished_cb); - set_interrupts_counter(L, 0); - - lua_callbacks(L)->interrupt = [](lua_State *L, int gc) - { - // skip if we're interrupting only for garbage collection - if (gc >= 0) - return; - - LLCoros::checkStop(); - check_interrupts_counter(L); - }; std::string text{std::istreambuf_iterator(in_file), {}}; auto [count, result] = L.expr(filename, text); if (result_cb) -- cgit v1.2.3 From 426dc4da45f91afbb1107aecf075e501e774e8ee Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 18 Apr 2024 16:53:12 -0400 Subject: Add llluamanager_test.cpp test that terminates runaway Lua script. Also tweak existing Lua interleaved-responses test to accommodate new Lua periodic suspend behavior. --- indra/newview/tests/llluamanager_test.cpp | 35 +++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index b907ac6619..cf1bf25b5c 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -400,9 +400,16 @@ namespace tut auto future = LLLUAmanager::startScriptLine(L, lua); auto replyname{ L.obtainListener()->getReplyName() }; auto& replypump{ LLEventPumps::instance().obtain(replyname) }; - // By the time leap.process() calls get_event_next() and wakes us up, - // we expect that both requester() coroutines have posted and are - // waiting for a reply. + // LuaState::expr() periodically interrupts a running chunk to ensure + // the rest of our coroutines get cycles. Nonetheless, for this test + // we have to wait until both requester() coroutines have posted and + // are waiting for a reply. + for (unsigned count=0; count < 100; ++count) + { + if (requests.size() == 2) + break; + llcoro::suspend(); + } ensure_equals("didn't get both requests", requests.size(), 2); // moreover, we expect they arrived in the order they were created ensure_equals("a wasn't first", requests[0]["name"].asString(), "a"); @@ -413,7 +420,7 @@ namespace tut replypump.post(llsd::map("name", "not special")); // now respond to requester(a) replypump.post(requests[0]); - // tell leap.process() we're done + // tell leap we're done replypump.post(LLSD()); auto [count, result] = future.get(); ensure_equals("leap.lua: " + result.asString(), count, 0); @@ -438,4 +445,24 @@ namespace tut ensure_equals("unexpected killed Lua script error", result.asString(), "viewer is stopping"); } + + template<> template<> + void object::test<8>() + { + set_test_name("stop looping Lua script"); + const std::string desc("looping Lua script should terminate"); + const std::string lua( + "-- " + desc + "\n" + "\n" + "while true do\n" + " x = 1\n" + "end\n" + ); + LuaState L; + auto [count, result] = LLLUAmanager::waitScriptLine(L, lua); + // We expect the above erroneous script has been forcibly terminated + // because it ran too long without doing any actual work. + ensure_equals(desc + " count: " + result.asString(), count, -1); + ensure_contains(desc + " result", result.asString(), "terminated"); + } } // namespace tut -- cgit v1.2.3 From bb1f3f08cf93facbf926e57384674441be7e2884 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Mon, 22 Apr 2024 14:56:52 +0300 Subject: Add demo script with idle and notification interactions --- indra/llui/llluafloater.cpp | 16 ++++++++-- indra/llui/llluafloater.h | 2 ++ indra/newview/llvoavatar.cpp | 30 +++++++++++++++++++ indra/newview/scripts/lua/LLNotification.lua | 15 ++++++++++ .../newview/scripts/lua/luafloater_speedometer.xml | 35 ++++++++++++++++++++++ .../scripts/lua/test_luafloater_speedometer.lua | 26 ++++++++++++++++ 6 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 indra/newview/scripts/lua/LLNotification.lua create mode 100644 indra/newview/scripts/lua/luafloater_speedometer.xml create mode 100644 indra/newview/scripts/lua/test_luafloater_speedometer.lua (limited to 'indra') diff --git a/indra/llui/llluafloater.cpp b/indra/llui/llluafloater.cpp index e584a67a00..62e77fd556 100644 --- a/indra/llui/llluafloater.cpp +++ b/indra/llui/llluafloater.cpp @@ -35,6 +35,7 @@ #include "lltexteditor.h" const std::string LISTENER_NAME("LLLuaFloater"); +const F32 IDLE_INTERVAL = 0.5; std::set EVENT_LIST = { "commit", @@ -47,14 +48,16 @@ std::set EVENT_LIST = { "right_mouse_up", "post_build", "floater_close", - "keystroke" + "keystroke", + "idle" }; LLLuaFloater::LLLuaFloater(const LLSD &key) : LLFloater(key), mDispatchListener(LLUUID::generateNewID().asString(), "action"), mReplyPumpName(key["reply"].asString()), - mReqID(key) + mReqID(key), + mIdleTimer(new LLTimer()) { auto ctrl_lookup = [this](const LLSD &event, std::function cb) { @@ -166,6 +169,15 @@ BOOL LLLuaFloater::postBuild() return true; } +void LLLuaFloater::draw() +{ + if (mIdleTimer->checkExpirationAndReset(IDLE_INTERVAL)) + { + postEvent(LLSD(), "idle"); + } + LLFloater::draw(); +} + void LLLuaFloater::onClose(bool app_quitting) { postEvent(llsd::map("app_quitting", app_quitting), "floater_close"); diff --git a/indra/llui/llluafloater.h b/indra/llui/llluafloater.h index ccc3ccb39b..36e65ac7fc 100644 --- a/indra/llui/llluafloater.h +++ b/indra/llui/llluafloater.h @@ -36,6 +36,7 @@ public: LLLuaFloater(const LLSD &key); BOOL postBuild(); virtual ~LLLuaFloater(); + void draw(); void registerCallback(const std::string &ctrl_name, const std::string &event); void onClose(bool app_quitting); @@ -48,6 +49,7 @@ public: private: LLReqID mReqID; LLDispatchListener mDispatchListener; + std::unique_ptr mIdleTimer; std::string mReplyPumpName; }; diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 9579a5e4b1..3074990a9b 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -114,6 +114,7 @@ #include "llskinningutil.h" #include "llperfstats.h" +#include "lleventapi.h" #include @@ -11599,4 +11600,33 @@ F32 LLVOAvatar::getAverageGPURenderTime() return ret; } +class LLVOAvatarListener : public LLEventAPI +{ + public: + LLVOAvatarListener() : LLEventAPI("LLVOAvatar", "LLVOAvatar listener to retrieve avatar info") + { + add("getSpeed", + "Return the avatar movement speed in the XY plane", + &LLVOAvatarListener::getSpeed, + LLSD().with("reply", LLSD())); + add("isInAir", + "Return the info whether avatar is in the air, and if so the time in the air", + &LLVOAvatarListener::isInAir, + LLSD().with("reply", LLSD())); + } + + private: + void getSpeed(const LLSD &request) const + { + LLVector3 avatar_velocity = gAgentAvatarp->getCharacterVelocity() * gAgentAvatarp->getTimeDilation(); + avatar_velocity.mV[VZ] = 0.f; + Response response(llsd::map("value", avatar_velocity.magVec()), request); + } + void isInAir(const LLSD &request) const + { + Response response(llsd::map("value", gAgentAvatarp->mInAir, + "duration", gAgentAvatarp->mInAir ? gAgentAvatarp->mTimeInAir.getElapsedTimeF32() : 0), request); + } +}; +static LLVOAvatarListener VOAvatarListener; diff --git a/indra/newview/scripts/lua/LLNotification.lua b/indra/newview/scripts/lua/LLNotification.lua new file mode 100644 index 0000000000..f47730d1cc --- /dev/null +++ b/indra/newview/scripts/lua/LLNotification.lua @@ -0,0 +1,15 @@ +-- Engage the LLNotificationsListener LLEventAPI + +leap = require 'leap' + +local LLNotification = {} + +function LLNotification.add(name, substitutions) + leap.send('LLNotifications', {op='requestAdd', name=name, substitutions=substitutions}) +end + +function LLNotification.requestAdd(name, substitutions) + return leap.request('LLNotifications', {op='requestAdd', name=name, substitutions=substitutions})['response'] +end + +return LLNotification diff --git a/indra/newview/scripts/lua/luafloater_speedometer.xml b/indra/newview/scripts/lua/luafloater_speedometer.xml new file mode 100644 index 0000000000..54b99c7d48 --- /dev/null +++ b/indra/newview/scripts/lua/luafloater_speedometer.xml @@ -0,0 +1,35 @@ + + + + + m/s + + diff --git a/indra/newview/scripts/lua/test_luafloater_speedometer.lua b/indra/newview/scripts/lua/test_luafloater_speedometer.lua new file mode 100644 index 0000000000..610401ae44 --- /dev/null +++ b/indra/newview/scripts/lua/test_luafloater_speedometer.lua @@ -0,0 +1,26 @@ +local Floater = require 'Floater' +local startup = require 'startup' +inspect = require 'inspect' +leap = require 'leap' +LLNotification = require 'LLNotification' +local max_speed = 0 +local flt = Floater:new("luafloater_speedometer.xml") +startup.wait('STATE_STARTED') + +function flt:floater_close(event_data) + msg = "Registered max speed: " .. string.format("%.2f", max_speed) .. " m/s"; + LLNotification.add('SystemMessageTip', {MESSAGE = msg}) +end + +function flt:idle(event_data) + local speed = leap.request('LLVOAvatar', {op='getSpeed'})['value'] + flt:post({action="set_value", ctrl_name="speed_lbl", value = string.format("%.2f", speed)}) + max_speed=math.max(max_speed, speed) +end + +msg = 'Are you sure you want to run this "speedometer" script?' +response = LLNotification.requestAdd('GenericAlertYesCancel', {MESSAGE = msg}) + +if response.OK_okcancelbuttons then + flt:show() +end -- cgit v1.2.3 From a33a9d29380e6c1a0a9cc539be309d47adef4acf Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 25 Apr 2024 13:58:15 -0400 Subject: Adapt llimageworker_test for updated virtual method API. This was a broken test that got all the way to viewer release and the main branch. --- indra/llimage/tests/llimageworker_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llimage/tests/llimageworker_test.cpp b/indra/llimage/tests/llimageworker_test.cpp index 0a97b739b0..ffcd7d257f 100644 --- a/indra/llimage/tests/llimageworker_test.cpp +++ b/indra/llimage/tests/llimageworker_test.cpp @@ -98,7 +98,7 @@ namespace tut done = res; *done = false; } - virtual void completed(bool success, LLImageRaw* raw, LLImageRaw* aux) + virtual void completed(bool success, LLImageRaw* raw, LLImageRaw* aux, U32) { *done = true; } -- cgit v1.2.3 From 825c67612ce5ee6544055d82b337911050e86e75 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 25 Apr 2024 14:00:06 -0400 Subject: Add missing #include "stringize.h" Also change from boost::hof::is_invocable() to std::is_invocable(). --- indra/llcommon/lleventdispatcher.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index a82bc7a69b..3d7808ac31 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -35,7 +35,6 @@ #include #include #include -#include // until C++17, when we get std::is_invocable #include #include // std::function #include // std::unique_ptr @@ -48,6 +47,7 @@ #include "llevents.h" #include "llptrto.h" #include "llsdutil.h" +#include "stringize.h" class LLSD; @@ -99,7 +99,7 @@ public: template ::value + std::is_invocable::value >::type> void add(const std::string& name, const std::string& desc, @@ -296,7 +296,7 @@ public: */ template () + ! std::is_invocable() >::type> void add(const std::string& name, const std::string& desc, @@ -338,7 +338,7 @@ public: template::value && - ! boost::hof::is_invocable::value + ! std::is_invocable::value >::type> void add(const std::string& name, const std::string& desc, Function f, const LLSD& params, const LLSD& defaults=LLSD()); -- cgit v1.2.3 From 0bcbe6f850580306d3f665e459873adf736c37a2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 25 Apr 2024 14:01:50 -0400 Subject: Resolve WorkQueue::waitForResult() merge glitch. --- indra/llcommon/workqueue.h | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h index 03c0494c1f..c4435cec40 100644 --- a/indra/llcommon/workqueue.h +++ b/indra/llcommon/workqueue.h @@ -115,33 +115,29 @@ namespace LL ARGS&&... args); /** - * Post work to be run at a specified time, blocking the calling - * coroutine until then, returning the result to caller on completion. - * Optional final argument is TimePoint for WorkSchedule. + * Post work, blocking the calling coroutine, returning the result to + * caller on completion. Optional final argument is TimePoint for + * WorkSchedule. * * In general, we assume that each thread's default coroutine is busy * servicing its WorkQueue or whatever. To try to prevent mistakes, we * forbid calling waitForResult() from a thread's default coroutine. */ template - auto waitForResult(CALLABLE&& callable, ARGS&&... args); - - /** - * Post work, blocking the calling coroutine until then, returning the - * result to caller on completion. - */ - template - auto waitForResult_(CALLABLE&& callable) + auto waitForResult(CALLABLE&& callable, ARGS&&... args) { - return waitForResult_(TimePoint::clock::now(), std::forward(callable)); + checkCoroutine("waitForResult()"); + return waitForResult_(std::forward(callable), + std::forward(args)...); } /** - * Post work to be run at a specified time, blocking the calling - * coroutine until then, returning the result to caller on completion. + * Post work, blocking the calling coroutine, returning the result to + * caller on completion. Optional final argument is TimePoint for + * WorkSchedule. */ - template - auto waitForResult_(const TimePoint& time, CALLABLE&& callable); + template + auto waitForResult_(CALLABLE&& callable, ARGS&&... args); /*--------------------------- worker API ---------------------------*/ @@ -634,7 +630,7 @@ namespace LL }; template - auto WorkQueueBase::waitForResult(CALLABLE&& callable, ARGS&&... args) + auto WorkQueueBase::waitForResult_(CALLABLE&& callable, ARGS&&... args) { // derive callable's return type so we can specialize for void return WaitForResult(callable)())>() -- cgit v1.2.3 From 9bf5fcd225ada9dcd63fc5710f58013e7839df09 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 26 Apr 2024 09:22:39 -0400 Subject: Remove unused newview/llcallbacklist.cpp: real one is in llcommon. newview/llcallbacklist.cpp has no corresponding .h file, it isn't referenced by newview/CMakeLists.txt, and its removal doesn't affect the build. See llcommon/llcallbacklist.{h,cpp} for the real functionality. (cherry picked from commit 8e53d6ff4c6594f014f456b0ba9ebf86ac91f6bc) --- indra/newview/llcallbacklist.cpp | 305 --------------------------------------- 1 file changed, 305 deletions(-) delete mode 100644 indra/newview/llcallbacklist.cpp (limited to 'indra') diff --git a/indra/newview/llcallbacklist.cpp b/indra/newview/llcallbacklist.cpp deleted file mode 100644 index 59ecbdd0ea..0000000000 --- a/indra/newview/llcallbacklist.cpp +++ /dev/null @@ -1,305 +0,0 @@ -/** - * @file llcallbacklist.cpp - * @brief A simple list of callback functions to call. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llcallbacklist.h" -#include "lleventtimer.h" - -// Library includes -#include "llerror.h" - - -// -// Globals -// -LLCallbackList gIdleCallbacks; - -// -// Member functions -// - -LLCallbackList::LLCallbackList() -{ - // nothing -} - -LLCallbackList::~LLCallbackList() -{ -} - - -void LLCallbackList::addFunction( callback_t func, void *data) -{ - if (!func) - { - LL_ERRS() << "LLCallbackList::addFunction - function is NULL" << LL_ENDL; - return; - } - - // only add one callback per func/data pair - callback_pair_t t(func, data); - callback_list_t::iterator iter = std::find(mCallbackList.begin(), mCallbackList.end(), t); - if (iter == mCallbackList.end()) - { - mCallbackList.push_back(t); - } -} - - -BOOL LLCallbackList::containsFunction( callback_t func, void *data) -{ - callback_pair_t t(func, data); - callback_list_t::iterator iter = std::find(mCallbackList.begin(), mCallbackList.end(), t); - if (iter != mCallbackList.end()) - { - return TRUE; - } - else - { - return FALSE; - } -} - - -BOOL LLCallbackList::deleteFunction( callback_t func, void *data) -{ - callback_pair_t t(func, data); - callback_list_t::iterator iter = std::find(mCallbackList.begin(), mCallbackList.end(), t); - if (iter != mCallbackList.end()) - { - mCallbackList.erase(iter); - return TRUE; - } - else - { - return FALSE; - } -} - - -void LLCallbackList::deleteAllFunctions() -{ - mCallbackList.clear(); -} - - -void LLCallbackList::callFunctions() -{ - for (callback_list_t::iterator iter = mCallbackList.begin(); iter != mCallbackList.end(); ) - { - callback_list_t::iterator curiter = iter++; - curiter->first(curiter->second); - } -} - -// Shim class to allow arbitrary boost::bind -// expressions to be run as one-time idle callbacks. -class OnIdleCallbackOneTime -{ -public: - OnIdleCallbackOneTime(nullary_func_t callable): - mCallable(callable) - { - } - static void onIdle(void *data) - { - gIdleCallbacks.deleteFunction(onIdle, data); - OnIdleCallbackOneTime* self = reinterpret_cast(data); - self->call(); - delete self; - } - void call() - { - mCallable(); - } -private: - nullary_func_t mCallable; -}; - -void doOnIdleOneTime(nullary_func_t callable) -{ - OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable); - gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor); -} - -// Shim class to allow generic boost functions to be run as -// recurring idle callbacks. Callable should return true when done, -// false to continue getting called. -class OnIdleCallbackRepeating -{ -public: - OnIdleCallbackRepeating(bool_func_t callable): - mCallable(callable) - { - } - // Will keep getting called until the callable returns true. - static void onIdle(void *data) - { - OnIdleCallbackRepeating* self = reinterpret_cast(data); - bool done = self->call(); - if (done) - { - gIdleCallbacks.deleteFunction(onIdle, data); - delete self; - } - } - bool call() - { - return mCallable(); - } -private: - bool_func_t mCallable; -}; - -void doOnIdleRepeating(bool_func_t callable) -{ - OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable); - gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor); -} - -class NullaryFuncEventTimer: public LLEventTimer -{ -public: - NullaryFuncEventTimer(nullary_func_t callable, F32 seconds): - LLEventTimer(seconds), - mCallable(callable) - { - } - -private: - BOOL tick() - { - mCallable(); - return TRUE; - } - - nullary_func_t mCallable; -}; - -// Call a given callable once after specified interval. -void doAfterInterval(nullary_func_t callable, F32 seconds) -{ - new NullaryFuncEventTimer(callable, seconds); -} - -class BoolFuncEventTimer: public LLEventTimer -{ -public: - BoolFuncEventTimer(bool_func_t callable, F32 seconds): - LLEventTimer(seconds), - mCallable(callable) - { - } -private: - BOOL tick() - { - return mCallable(); - } - - bool_func_t mCallable; -}; - -// Call a given callable every specified number of seconds, until it returns true. -void doPeriodically(bool_func_t callable, F32 seconds) -{ - new BoolFuncEventTimer(callable, seconds); -} - -#ifdef _DEBUG - -void test1(void *data) -{ - S32 *s32_data = (S32 *)data; - LL_INFOS() << "testfunc1 " << *s32_data << LL_ENDL; -} - - -void test2(void *data) -{ - S32 *s32_data = (S32 *)data; - LL_INFOS() << "testfunc2 " << *s32_data << LL_ENDL; -} - - -void -LLCallbackList::test() -{ - S32 a = 1; - S32 b = 2; - LLCallbackList *list = new LLCallbackList; - - LL_INFOS() << "Testing LLCallbackList" << LL_ENDL; - - if (!list->deleteFunction(NULL)) - { - LL_INFOS() << "passed 1" << LL_ENDL; - } - else - { - LL_INFOS() << "error, removed function from empty list" << LL_ENDL; - } - - // LL_INFOS() << "This should crash" << LL_ENDL; - // list->addFunction(NULL); - - list->addFunction(&test1, &a); - list->addFunction(&test1, &a); - - LL_INFOS() << "Expect: test1 1, test1 1" << LL_ENDL; - list->callFunctions(); - - list->addFunction(&test1, &b); - list->addFunction(&test2, &b); - - LL_INFOS() << "Expect: test1 1, test1 1, test1 2, test2 2" << LL_ENDL; - list->callFunctions(); - - if (list->deleteFunction(&test1, &b)) - { - LL_INFOS() << "passed 3" << LL_ENDL; - } - else - { - LL_INFOS() << "error removing function" << LL_ENDL; - } - - LL_INFOS() << "Expect: test1 1, test1 1, test2 2" << LL_ENDL; - list->callFunctions(); - - list->deleteAllFunctions(); - - LL_INFOS() << "Expect nothing" << LL_ENDL; - list->callFunctions(); - - LL_INFOS() << "nothing :-)" << LL_ENDL; - - delete list; - - LL_INFOS() << "test complete" << LL_ENDL; -} - -#endif // _DEBUG -- cgit v1.2.3 From 0868427913e0a1411bc857b227dad82414e42457 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Mon, 29 Apr 2024 16:24:43 +0300 Subject: Allow changing debug settings via Lua script --- indra/llxml/llcontrol.cpp | 4 ++-- indra/llxml/llcontrol.h | 2 +- indra/newview/llappviewerlistener.cpp | 14 ++++++++++++++ indra/newview/llappviewerlistener.h | 2 ++ indra/newview/llfloatersettingsdebug.cpp | 20 ++++++++++++++++---- indra/newview/llfloatersettingsdebug.h | 4 ++++ .../skins/default/xui/en/floater_settings_debug.xml | 18 ++++++++++++++++-- 7 files changed, 55 insertions(+), 9 deletions(-) (limited to 'indra') diff --git a/indra/llxml/llcontrol.cpp b/indra/llxml/llcontrol.cpp index 2960ecf829..a2178ed77d 100644 --- a/indra/llxml/llcontrol.cpp +++ b/indra/llxml/llcontrol.cpp @@ -730,7 +730,7 @@ void LLControlGroup::setLLSD(const std::string& name, const LLSD& val) set(name, val); } -void LLControlGroup::setUntypedValue(const std::string& name, const LLSD& val) +void LLControlGroup::setUntypedValue(const std::string& name, const LLSD& val, bool saved_value) { if (name.empty()) { @@ -741,7 +741,7 @@ void LLControlGroup::setUntypedValue(const std::string& name, const LLSD& val) if (control) { - control->setValue(val); + control->setValue(val, saved_value); } else { diff --git a/indra/llxml/llcontrol.h b/indra/llxml/llcontrol.h index 0839c02c50..7e79e2f31e 100644 --- a/indra/llxml/llcontrol.h +++ b/indra/llxml/llcontrol.h @@ -278,7 +278,7 @@ public: void setLLSD(const std::string& name, const LLSD& val); // type agnostic setter that takes LLSD - void setUntypedValue(const std::string& name, const LLSD& val); + void setUntypedValue(const std::string& name, const LLSD& val, bool saved_value = true); // generic setter template void set(const std::string& name, const T& val) diff --git a/indra/newview/llappviewerlistener.cpp b/indra/newview/llappviewerlistener.cpp index 2380a8ebf0..bd0c6955b1 100644 --- a/indra/newview/llappviewerlistener.cpp +++ b/indra/newview/llappviewerlistener.cpp @@ -35,6 +35,7 @@ // external library headers // other Linden headers #include "llappviewer.h" +#include "llviewercontrol.h" LLAppViewerListener::LLAppViewerListener(const LLAppViewerGetter& getter): LLEventAPI("LLAppViewer", @@ -48,6 +49,10 @@ LLAppViewerListener::LLAppViewerListener(const LLAppViewerGetter& getter): add("forceQuit", "Quit abruptly", &LLAppViewerListener::forceQuit); + + add("setDebugSetting", + "Apply specified [\"value\"] to the debug [\"setting\"] (this change won't persist across sessions)", + &LLAppViewerListener::setDebugSetting, llsd::map("setting", LLSD(), "value", LLSD())); } void LLAppViewerListener::requestQuit(const LLSD& event) @@ -61,3 +66,12 @@ void LLAppViewerListener::forceQuit(const LLSD& event) LL_INFOS() << "Listener requested force quit" << LL_ENDL; mAppViewerGetter()->forceQuit(); } + +void LLAppViewerListener::setDebugSetting(const LLSD &event) +{ + auto setting_name = event["setting"].asString(); + auto value = event["value"]; + LL_WARNS("LLAppViewerListener") << "Changing debug setting \"" << setting_name << "\" to " << value << LL_ENDL; + //don't save this change between sesssions + gSavedSettings.setUntypedValue(setting_name, value, false); +} diff --git a/indra/newview/llappviewerlistener.h b/indra/newview/llappviewerlistener.h index 78c8b1909e..8b38636bd1 100644 --- a/indra/newview/llappviewerlistener.h +++ b/indra/newview/llappviewerlistener.h @@ -47,6 +47,8 @@ private: void requestQuit(const LLSD& event); void forceQuit(const LLSD& event); + void setDebugSetting(const LLSD &event); + LLAppViewerGetter mAppViewerGetter; }; diff --git a/indra/newview/llfloatersettingsdebug.cpp b/indra/newview/llfloatersettingsdebug.cpp index 3c7f341613..e1df6a4b1f 100644 --- a/indra/newview/llfloatersettingsdebug.cpp +++ b/indra/newview/llfloatersettingsdebug.cpp @@ -34,6 +34,7 @@ #include "llcolorswatch.h" #include "llviewercontrol.h" #include "lltexteditor.h" +#include "llclipboard.h" LLFloaterSettingsDebug::LLFloaterSettingsDebug(const LLSD& key) @@ -52,6 +53,8 @@ BOOL LLFloaterSettingsDebug::postBuild() enableResizeCtrls(true, false, true); mComment = getChild("comment_text"); + mSettingName = getChild("setting_name_txt"); + mCopyBtn = getChild("copy_btn"); getChild("filter_input")->setCommitCallback(boost::bind(&LLFloaterSettingsDebug::setSearchFilter, this, _2)); @@ -59,6 +62,8 @@ BOOL LLFloaterSettingsDebug::postBuild() mSettingList->setCommitOnSelectionChange(TRUE); mSettingList->setCommitCallback(boost::bind(&LLFloaterSettingsDebug::onSettingSelect, this)); + mCopyBtn->setCommitCallback([this](LLUICtrl *ctrl, const LLSD ¶m) { onClickCopy(); }); + updateList(); gSavedSettings.getControl("DebugSettingsHideDefault")->getCommitSignal()->connect(boost::bind(&LLFloaterSettingsDebug::updateList, this, false)); @@ -203,9 +208,10 @@ void LLFloaterSettingsDebug::updateControl(LLControlVariable* controlp) //hide combo box only for non booleans, otherwise this will result in the combo box closing every frame getChildView("boolean_combo")->setVisible( type == TYPE_BOOLEAN); getChildView("default_btn")->setVisible(true); - getChildView("setting_name_txt")->setVisible(true); - getChild("setting_name_txt")->setText(controlp->getName()); - getChild("setting_name_txt")->setToolTip(controlp->getName()); + mSettingName->setVisible(true); + mSettingName->setText(controlp->getName()); + mSettingName->setToolTip(controlp->getName()); + mCopyBtn->setVisible(true); mComment->setVisible(true); std::string old_text = mComment->getText(); @@ -632,7 +638,13 @@ void LLFloaterSettingsDebug::hideUIControls() getChildView("val_text")->setVisible(false); getChildView("default_btn")->setVisible(false); getChildView("boolean_combo")->setVisible(false); - getChildView("setting_name_txt")->setVisible(false); + mSettingName->setVisible(false); + mCopyBtn->setVisible(false); mComment->setVisible(false); } +void LLFloaterSettingsDebug::onClickCopy() +{ + std::string setting_name = mSettingName->getText(); + LLClipboard::instance().copyToClipboard(utf8str_to_wstring(setting_name), 0, setting_name.size()); +} diff --git a/indra/newview/llfloatersettingsdebug.h b/indra/newview/llfloatersettingsdebug.h index 888eaadcbd..6ff3e344b4 100644 --- a/indra/newview/llfloatersettingsdebug.h +++ b/indra/newview/llfloatersettingsdebug.h @@ -31,6 +31,7 @@ #include "llfloater.h" class LLScrollListCtrl; +class LLTextBox; class LLFloaterSettingsDebug : public LLFloater @@ -46,6 +47,7 @@ public: void onCommitSettings(); void onClickDefault(); + void onClickCopy(); bool matchesSearchFilter(std::string setting_name); bool isSettingHidden(LLControlVariable* control); @@ -67,6 +69,8 @@ private: protected: class LLTextEditor* mComment; + LLTextBox* mSettingName; + LLButton* mCopyBtn; std::string mSearchFilter; }; diff --git a/indra/newview/skins/default/xui/en/floater_settings_debug.xml b/indra/newview/skins/default/xui/en/floater_settings_debug.xml index a93be6a18d..6fd8f2b255 100644 --- a/indra/newview/skins/default/xui/en/floater_settings_debug.xml +++ b/indra/newview/skins/default/xui/en/floater_settings_debug.xml @@ -43,6 +43,20 @@ label="Setting" name="setting" /> + + + diff --git a/indra/newview/scripts/lua/require/LLAppearance.lua b/indra/newview/scripts/lua/require/LLAppearance.lua new file mode 100644 index 0000000000..165bb6d06f --- /dev/null +++ b/indra/newview/scripts/lua/require/LLAppearance.lua @@ -0,0 +1,37 @@ +leap = require 'leap' + +local LLAppearance = {} + +function LLAppearance.addOutfit(folder) + leap.request('LLAppearance', {op='wearOutfit', append = true, folder_id=folder}) +end + +function LLAppearance.replaceOutfit(folder) + leap.request('LLAppearance', {op='wearOutfit', append = false, folder_id=folder}) +end + +function LLAppearance.addOutfitByName(folder) + leap.request('LLAppearance', {op='wearOutfitByName', append = true, folder_name=folder}) +end + +function LLAppearance.replaceOutfitByName(folder) + leap.request('LLAppearance', {op='wearOutfitByName', append = false, folder_name=folder}) +end + +function LLAppearance.wearItem(item_id, replace) + leap.send('LLAppearance', {op='wearItem', replace = replace, item_id=item_id}) +end + +function LLAppearance.detachItem(item_id) + leap.send('LLAppearance', {op='detachItem', item_id=item_id}) +end + +function LLAppearance.getOutfitsList() + return leap.request('LLAppearance', {op='getOutfitsList'})['outfits'] +end + +function LLAppearance.getOutfitItems(id) + return leap.request('LLAppearance', {op='getOutfitItems', outfit_id = id})['items'] +end + +return LLAppearance diff --git a/indra/newview/scripts/lua/test_outfits_list.lua b/indra/newview/scripts/lua/test_outfits_list.lua index 5875fd51da..dd5f914402 100644 --- a/indra/newview/scripts/lua/test_outfits_list.lua +++ b/indra/newview/scripts/lua/test_outfits_list.lua @@ -1,26 +1,107 @@ local Floater = require 'Floater' local LLAppearance = require 'LLAppearance' local startup = require 'startup' +local inspect = require 'inspect' + +local SHOW_OUTFITS = true +local SELECTED_OUTFIT_ID = {} +local DATA_MAP = {} + +local wearables_lbl = 'Show wearables' +local outfits_lbl = 'Show outfits' +local replace_cof_lbl = 'Replace COF' +local add_cof_lbl = 'Add to COF' +local outfits_title = 'Outfits' +local wear_lbl = 'Wear item' +local detach_lbl = 'Detach item' local flt = Floater:new( "luafloater_outfits_list.xml", {outfits_list = {"double_click"}}) -function flt:post_build(event_data) - local outfits_map = LLAppearance.getOutfitsList() +function get_selected_id() + return flt:request({action="get_selected_id", ctrl_name='outfits_list'}).value +end + +function populate_list() + if SHOW_OUTFITS then + DATA_MAP = LLAppearance.getOutfitsList() + else + DATA_MAP = LLAppearance.getOutfitItems(SELECTED_OUTFIT_ID) + end + local action_data = {} action_data.action = "add_list_element" action_data.ctrl_name = "outfits_list" local outfits = {} - for uuid, name in pairs(outfits_map) do + for uuid, name in pairs(DATA_MAP) do table.insert(outfits, {value = uuid, columns={column = "outfit_name", value = name}}) end action_data.value = outfits - self:post(action_data) + flt:post(action_data) +end + +function set_label(btn_name, value) + flt:post({action="set_label", ctrl_name=btn_name, value=value}) +end + +function set_enabled(btn_name, value) + flt:post({action="set_enabled", ctrl_name=btn_name, value=value}) +end + +function update_labels() + if SHOW_OUTFITS then + set_label('select_btn', wearables_lbl) + set_label('replace_btn', replace_cof_lbl) + set_label('add_btn', add_cof_lbl) + + set_enabled('select_btn', false) + flt:post({action="set_title", value=outfits_title}) + else + set_label('select_btn', outfits_lbl) + set_label('replace_btn', wear_lbl) + set_label('add_btn', detach_lbl) + + set_enabled('select_btn', true) + flt:post({action="set_title", value=DATA_MAP[SELECTED_OUTFIT_ID]}) + end + + set_enabled('replace_btn', false) + set_enabled('add_btn', false) +end + +function flt:post_build(event_data) + populate_list() +end + +function flt:commit_replace_btn(event_data) + if SHOW_OUTFITS then + LLAppearance.replaceOutfit(get_selected_id()) + else + LLAppearance.wearItem(get_selected_id(), false) + end +end + +function flt:commit_add_btn(event_data) + if SHOW_OUTFITS then + LLAppearance.addOutfit(get_selected_id()) + else + LLAppearance.detachItem(get_selected_id()) + end +end + +function flt:commit_select_btn(event_data) + SHOW_OUTFITS = not SHOW_OUTFITS + SELECTED_OUTFIT_ID = get_selected_id() + update_labels() + self:post({action="clear_list", ctrl_name='outfits_list'}) + populate_list() end -function flt:double_click_outfits_list(event_data) - LLAppearance.replaceOutfit(event_data.value) +function flt:commit_outfits_list(event_data) + set_enabled('replace_btn', true) + set_enabled('add_btn', true) + set_enabled('select_btn', true) end startup.wait('STATE_STARTED') -- cgit v1.2.3 From b347ad5deb1c9abb210ac5da0534766bf5b6f2f0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jun 2024 18:06:13 -0400 Subject: Make test.cpp test driver recognize LOGTEST_testname. Setting LOGTEST=DEBUG, when many unit/integration tests must be rebuilt and run, can result in lots of unnecessary output. When we only want DEBUG log output from a specific test program, make test.cpp recognize an environment variable LOGTEST_testname, where 'testname' might be the full basename of the executable, or part of INTEGRATION_TEST_testname or PROJECT_foo_TEST_testname. When test.cpp notices a non-empty variable by that name, it behaves as if LOGTEST were set to that value. --- indra/test/test.cpp | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/test/test.cpp b/indra/test/test.cpp index 61a4eb07c5..3cbac41f4a 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -35,13 +35,14 @@ */ #include "linden_common.h" -#include "llerrorcontrol.h" -#include "lltut.h" #include "chained_callback.h" -#include "stringize.h" -#include "namedtempfile.h" +#include "fsyspath.h" +#include "llerrorcontrol.h" #include "lltrace.h" #include "lltracethreadrecorder.h" +#include "lltut.h" +#include "namedtempfile.h" +#include "stringize.h" #include "apr_pools.h" #include "apr_getopt.h" @@ -545,6 +546,27 @@ int main(int argc, char **argv) // LOGTEST overrides default, but can be overridden by --debug. const char* LOGTEST = getenv("LOGTEST"); + // Sometimes we must rebuild much of the viewer before we get to the + // specific test we want to monitor, and some viewer integration tests are + // quite verbose. In addition to noticing plain LOGTEST= (for all tests), + // also notice LOGTEST_progname= (for a specific test). + std::string basename{ fsyspath(argv[0]).stem() }; + // don't make user set LOGTEST_INTEGRATION_TEST_progname or (worse) + // LOGTEST_PROJECT_foo_TEST_bar -- only LOGTEST_progname or LOGTEST_bar + auto _TEST_ = basename.find("_TEST_"); + if (_TEST_ != std::string::npos) + { + basename.erase(0, _TEST_+6); + } + std::string LOGTEST_prog_key{ "LOGTEST_" + basename }; + const char* LOGTEST_prog = getenv(LOGTEST_prog_key.c_str()); +// std::cout << LOGTEST_prog_key << "='" << (LOGTEST_prog? LOGTEST_prog : "") << "'" << std::endl; + if (LOGTEST_prog && *LOGTEST_prog) + { + LOGTEST = LOGTEST_prog; + std::cout << "LOGTEST='" << LOGTEST << "' from " << LOGTEST_prog_key << std::endl; + } + // values used for options parsing apr_status_t apr_err; const char* opt_arg = NULL; -- cgit v1.2.3 From 6ee98f4e9b5ea9ade06056a9d1f4c1e0b1c00b44 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jun 2024 19:01:28 -0400 Subject: Make lua_emplace() use Luau userdata tags with destructors. It turns out that Luau does not honor PUC-Rio Lua's __gc metafunction, so despite elaborate measures, the previous lua_emplace() implementation would not have destroyed the contained C++ T object when the resulting userdata object was garbage-collected. Moreover, using LL.atexit() as the mechanism to destroy lua_emplace() userdata objects (e.g. LuaListener) would have been slightly fragile because we also want to use LL.atexit() to make the final fiber.run() call, when appropriate. Introducing an order dependency between fiber.run() and the LuaListener destructor would not be robust. Both of those problems are addressed by leveraging one of Luau's extensions over PUC-Rio Lua. A Luau userdata object can have an int tag; and a tag can have an associated C++ destructor function. When any userdata object bearing that tag is garbage-collected, Luau will call that destructor; and Luau's lua_close() function destroys all userdata objects. The resulting lua_emplace() and lua_toclass() code is far simpler. It only remains to generate a distinct int tag value for each different C++ type passed to the lua_emplace() template. unordered_map addresses that need. --- indra/llcommon/lua_function.h | 223 +++++++++--------------------------------- 1 file changed, 48 insertions(+), 175 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 9cdd5665dc..284f911fa8 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -21,8 +21,9 @@ #include "stringize.h" #include // std::uncaught_exceptions() #include // std::shared_ptr -#include +#include #include +#include #include // std::pair class LuaListener; @@ -196,28 +197,33 @@ int name##_luasub::call(lua_State* L) *****************************************************************************/ namespace { -// this closure function retrieves its bound argument to pass to -// lua_emplace_gc() -template -int lua_emplace_call_gc(lua_State* L); -// this will be the function called by the new userdata's metatable's __gc() -template -int lua_emplace_gc(lua_State* L); -// name by which we'll store the new userdata's metatable in the Registry -template -std::string lua_emplace_metaname(const std::string& Tname = LLError::Log::classname()); +// If we start engaging lua_emplace() from more than one thread, type_tags +// will need locking. +std::unordered_map type_tags; + +// find or create a new Luau userdata "tag" for type T +template +int type_tag() +{ + // The first time we encounter a given type T, assign a new distinct tag + // value based on the number of previously-created tags. But avoid tag 0, + // which is evidently the default for userdata objects created without + // explicit tags. Don't try to destroy a nonexistent T object in a random + // userdata object! + auto [entry, created] = type_tags.emplace(std::type_index(typeid(T)), int(type_tags.size()+1)); + // Luau only permits up to LUA_UTAG_LIMIT distinct userdata tags (ca. 128) + llassert(entry->second < LUA_UTAG_LIMIT); + return entry->second; +} } // anonymous namespace /** * On the stack belonging to the passed lua_State, push a Lua userdata object - * with a newly-constructed C++ object std::optional(args...). The new - * userdata has a metadata table with a __gc() function to ensure that when - * the userdata instance is garbage-collected, ~T() is called. Also call - * LL.atexit(lua_emplace_call_gc(object)) to make ~LuaState() call ~T(). - * - * We wrap the userdata object as std::optional so we can explicitly - * destroy the contained T, and detect that we've done so. + * containing a newly-constructed C++ object T(args...). The userdata has a + * Luau destructor guaranteeing that the new T instance is destroyed when the + * userdata is garbage-collected, no later than when the LuaState is + * destroyed. * * Usage: * lua_emplace(L, T constructor args...); @@ -226,178 +232,45 @@ std::string lua_emplace_metaname(const std::string& Tname = LLError::Log::classn template void lua_emplace(lua_State* L, ARGS&&... args) { - using optT = std::optional; - luaL_checkstack(L, 5, nullptr); - auto ptr = lua_newuserdata(L, sizeof(optT)); + luaL_checkstack(L, 1, nullptr); + int tag{ type_tag() }; + if (! lua_getuserdatadtor(L, tag)) + { + // We haven't yet told THIS lua_State the destructor to use for this tag. + lua_setuserdatadtor( + L, tag, + [](lua_State*, void* ptr) + { + // destroy the contained T instance + static_cast(ptr)->~T(); + }); + } + auto ptr = lua_newuserdatatagged(L, sizeof(T), tag); // stack is uninitialized userdata // For now, assume (but verify) that lua_newuserdata() returns a // conservatively-aligned ptr. If that turns out not to be the case, we // might have to discard the new userdata, overallocate its successor and // perform manual alignment -- but only if we must. - llassert((uintptr_t(ptr) % alignof(optT)) == 0); + llassert((uintptr_t(ptr) % alignof(T)) == 0); // Construct our T there using placement new - new (ptr) optT(std::in_place, std::forward(args)...); - // stack is now initialized userdata containing our T instance - - // Find or create the metatable shared by all userdata instances holding - // C++ type T. We want it to be shared across instances, but it must be - // type-specific because its __gc field is lua_emplace_gc. - auto Tname{ LLError::Log::classname() }; - auto metaname{ lua_emplace_metaname(Tname) }; - if (luaL_newmetatable(L, metaname.c_str())) - { - // just created it: populate it - auto gcname{ stringize("lua_emplace_gc<", Tname, ">") }; - lua_pushcfunction(L, lua_emplace_gc, gcname.c_str()); - // stack is userdata, metatable, lua_emplace_gc - lua_setfield(L, -2, "__gc"); - } - // stack is userdata, metatable - lua_setmetatable(L, -2); - // Stack is now userdata, initialized with T(args), - // with metatable.__gc pointing to lua_emplace_gc. - - // But wait, there's more! Use our atexit() function to ensure that this - // C++ object is eventually destroyed even if the garbage collector never - // gets around to it. - lua_getglobal(L, "LL"); - // stack contains userdata, LL - lua_getfield(L, -1, "atexit"); - // stack contains userdata, LL, LL.atexit - // ditch LL - lua_replace(L, -2); - // stack contains userdata, LL.atexit - - // We have a bit of a problem here. We want to allow the garbage collector - // to collect the userdata if it must; but we also want to register a - // cleanup function to destroy the value if (usual case) it has NOT been - // garbage-collected. The problem is that if we bind into atexit()'s queue - // a strong reference to the userdata, we ensure that the garbage - // collector cannot collect it, making our metatable with __gc function - // completely moot. And we must assume that lua_pushcclosure() binds a - // strong reference to each value passed as a closure. - - // The solution is to use one more indirection: create a weak table whose - // sole entry is the userdata. If all other references to the new userdata - // are forgotten, so the only remaining reference is the weak table, the - // userdata can be collected. Then we can bind that weak table as the - // closure value for our cleanup function. - // The new weak table will have at most 1 array value, 0 other keys. - lua_createtable(L, 1, 0); - // stack contains userdata, LL.atexit, weak_table - if (luaL_newmetatable(L, "weak_values")) - { - // stack contains userdata, LL.atexit, weak_table, weak_values - // just created "weak_values" metatable: populate it - // Registry.weak_values = {__mode="v"} - lua_pushliteral(L, "v"); - // stack contains userdata, LL.atexit, weak_table, weak_values, "v" - lua_setfield(L, -2, "__mode"); - } - // stack contains userdata, LL.atexit, weak_table, weak_values - // setmetatable(weak_table, weak_values) - lua_setmetatable(L, -2); - // stack contains userdata, LL.atexit, weak_table - lua_pushinteger(L, 1); - // stack contains userdata, LL.atexit, weak_table, 1 - // duplicate userdata - lua_pushvalue(L, -4); - // stack contains userdata, LL.atexit, weak_table, 1, userdata - // weak_table[1] = userdata - lua_settable(L, -3); - // stack contains userdata, LL.atexit, weak_table - - // push a closure binding (lua_emplace_call_gc, weak_table) - auto callgcname{ stringize("lua_emplace_call_gc<", Tname, ">") }; - lua_pushcclosure(L, lua_emplace_call_gc, callgcname.c_str(), 1); - // stack contains userdata, LL.atexit, closure - // Call LL.atexit(closure) - lua_call(L, 1, 0); - // stack contains userdata -- return that -} - -namespace { - -// passed to LL.atexit(closure(lua_emplace_call_gc, weak_table{userdata})); -// retrieves bound userdata to pass to lua_emplace_gc() -template -int lua_emplace_call_gc(lua_State* L) -{ - luaL_checkstack(L, 2, nullptr); - // retrieve the first (only) bound upvalue and push to stack top - lua_pushvalue(L, lua_upvalueindex(1)); - // This is the weak_table bound by lua_emplace(). Its one and only - // entry should be the lua_emplace() userdata -- unless userdata has - // been garbage collected. Retrieve weak_table[1]. - lua_pushinteger(L, 1); - // stack contains weak_table, 1 - lua_gettable(L, -2); - // stack contains weak_table, weak_table[1] - // If our userdata was garbage-collected, there is no weak_table[1], - // and we just retrieved nil. - if (lua_isnil(L, -1)) - { - lua_pop(L, 2); - return 0; - } - // stack contains weak_table, userdata - // ditch weak_table - lua_replace(L, -2); - // pass userdata to lua_emplace_gc() - return lua_emplace_gc(L); -} - -// set as metatable(userdata).__gc to be called by the garbage collector -template -int lua_emplace_gc(lua_State* L) -{ - using optT = std::optional; - // We're called with userdata on the stack holding an instance of type T. - auto ptr = lua_touserdata(L, -1); - llassert(ptr); - // Destroy the T object contained in optT at the void* address ptr. If - // in future lua_emplace() must manually align our optT* within the - // Lua-provided void*, derive optT* from ptr. - static_cast(ptr)->reset(); - // pop the userdata - lua_pop(L, 1); - return 0; -} - -template -std::string lua_emplace_metaname(const std::string& Tname) -{ - return stringize("lua_emplace_", Tname, "_meta"); + new (ptr) T(std::forward(args)...); + // stack is now initialized userdata containing our T instance -- return + // that } -} // anonymous namespace - /** * If the value at the passed acceptable index is a full userdata created by - * lua_emplace() -- that is, the userdata contains a non-empty - * std::optional -- return a pointer to the contained T instance. Otherwise - * (index is not a full userdata; userdata is not of type std::optional; - * std::optional is empty) return nullptr. + * lua_emplace(), return a pointer to the contained T instance. Otherwise + * (index is not a full userdata; userdata is not of type T) return nullptr. */ template T* lua_toclass(lua_State* L, int index) { - using optT = std::optional; - // recreate the name lua_emplace() uses for its metatable - auto metaname{ lua_emplace_metaname() }; // get void* pointer to userdata (if that's what it is) - void* ptr{ luaL_checkudata(L, index, metaname.c_str()) }; - if (! ptr) - return nullptr; - // Derive the optT* from ptr. If in future lua_emplace() must manually - // align our optT* within the Lua-provided void*, adjust accordingly. - optT* tptr(static_cast(ptr)); - // make sure our optT isn't empty - if (! *tptr) - return nullptr; - // looks like we still have a non-empty optT: return the *address* of the - // value() reference - return &tptr->value(); + void* ptr{ lua_touserdatatagged(L, index, type_tag()) }; + // Derive the T* from ptr. If in future lua_emplace() must manually + // align our T* within the Lua-provided void*, adjust accordingly. + return static_cast(ptr); } /***************************************************************************** -- cgit v1.2.3 From 0cc7436be1f57299384c5acad5d32e13f2f4d1cf Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jun 2024 19:47:00 -0400 Subject: Introduce TypeTag template whose int value differs for each T. This replaces type_tag(), which searched and possibly extended the type_tags unordered_map at runtime. If we called lua_emplace() from different threads, that would require locking type_tags. In contrast, the compiler must instantiate a distinct TypeTag for every distinct T passed to lua_emplace(), so each gets a distinct value at static initialization time. No locking is required; no lookup; no allocations. Add a test to llluamanager_test.cpp to verify that each distinct T passed to lua_emplace() gets its own TypeTag::value, and that each gets its own destructor -- but that different lua_emplace() calls with the same T share the same TypeTag::value and the same destructor. --- indra/llcommon/lua_function.cpp | 2 ++ indra/llcommon/lua_function.h | 44 ++++++++++++++++++------------- indra/newview/tests/llluamanager_test.cpp | 35 ++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 18 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 255385b8c4..2d08de68c5 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -38,6 +38,8 @@ const S32 INTERRUPTS_SUSPEND_LIMIT = 100; #define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n))) #define lua_rawlen lua_objlen +int DistinctInt::mValues{0}; + /***************************************************************************** * luau namespace *****************************************************************************/ diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 284f911fa8..c32a586d79 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -195,26 +195,34 @@ int name##_luasub::call(lua_State* L) /***************************************************************************** * lua_emplace(), lua_toclass() *****************************************************************************/ -namespace { +// Every instance of DistinctInt has a different int value, barring int +// wraparound. +class DistinctInt +{ +public: + DistinctInt(): mValue(++mValues) {} + int get() const { return mValue; } + operator int() const { return mValue; } +private: + static int mValues; + int mValue; +}; -// If we start engaging lua_emplace() from more than one thread, type_tags -// will need locking. -std::unordered_map type_tags; +namespace { -// find or create a new Luau userdata "tag" for type T template -int type_tag() +struct TypeTag { - // The first time we encounter a given type T, assign a new distinct tag - // value based on the number of previously-created tags. But avoid tag 0, - // which is evidently the default for userdata objects created without - // explicit tags. Don't try to destroy a nonexistent T object in a random - // userdata object! - auto [entry, created] = type_tags.emplace(std::type_index(typeid(T)), int(type_tags.size()+1)); - // Luau only permits up to LUA_UTAG_LIMIT distinct userdata tags (ca. 128) - llassert(entry->second < LUA_UTAG_LIMIT); - return entry->second; -} + // For (std::is_same), &TypeTag::value == &TypeTag::value. + // For (! std::is_same), &TypeTag::value != &TypeTag::value. + // And every distinct instance of DistinctInt has a distinct value. + // Therefore, TypeTag::value is an int uniquely associated with each + // distinct T. + static DistinctInt value; +}; + +template +DistinctInt TypeTag::value; } // anonymous namespace @@ -233,7 +241,7 @@ template void lua_emplace(lua_State* L, ARGS&&... args) { luaL_checkstack(L, 1, nullptr); - int tag{ type_tag() }; + int tag{ TypeTag::value }; if (! lua_getuserdatadtor(L, tag)) { // We haven't yet told THIS lua_State the destructor to use for this tag. @@ -267,7 +275,7 @@ template T* lua_toclass(lua_State* L, int index) { // get void* pointer to userdata (if that's what it is) - void* ptr{ lua_touserdatatagged(L, index, type_tag()) }; + void* ptr{ lua_touserdatatagged(L, index, TypeTag::value) }; // Derive the T* from ptr. If in future lua_emplace() must manually // align our T* within the Lua-provided void*, adjust accordingly. return static_cast(ptr); diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index 2d525f7913..d3fc70dfd5 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -465,4 +465,39 @@ namespace tut ensure_equals(desc + " count: " + result.asString(), count, -1); ensure_contains(desc + " result", result.asString(), "terminated"); } + + template + struct Visible + { + Visible(T name): name(name) + { + LL_INFOS() << "Visible<" << LLError::Log::classname() << ">('" << name << "')" << LL_ENDL; + } + Visible(const Visible&) = delete; + Visible& operator=(const Visible&) = delete; + ~Visible() + { + LL_INFOS() << "~Visible<" << LLError::Log::classname() << ">('" << name << "')" << LL_ENDL; + } + T name; + }; + + template<> template<> + void object::test<9>() + { + set_test_name("track distinct lua_emplace() types"); + LuaState L; + lua_emplace>(L, "std::string 0"); + int st0tag = lua_userdatatag(L, -1); + lua_emplace>(L, "const char* 0"); + int cp0tag = lua_userdatatag(L, -1); + lua_emplace>(L, "std::string 1"); + int st1tag = lua_userdatatag(L, -1); + lua_emplace>(L, "const char* 1"); + int cp1tag = lua_userdatatag(L, -1); + lua_settop(L, 0); + ensure_equals("lua_emplace() tags diverge", st0tag, st1tag); + ensure_equals("lua_emplace() tags diverge", cp0tag, cp1tag); + ensure_not_equals("lua_emplace<>() tags collide", st0tag, cp0tag); + } } // namespace tut -- cgit v1.2.3 From 982ea7fb796924877e95bee2f9ba6b2296219139 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jun 2024 23:11:18 -0400 Subject: Work around VS refusal to initialize a string --- indra/test/test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/test/test.cpp b/indra/test/test.cpp index 3cbac41f4a..22f9ccf334 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -550,7 +550,7 @@ int main(int argc, char **argv) // specific test we want to monitor, and some viewer integration tests are // quite verbose. In addition to noticing plain LOGTEST= (for all tests), // also notice LOGTEST_progname= (for a specific test). - std::string basename{ fsyspath(argv[0]).stem() }; + std::string basename(fsyspath(argv[0]).stem()); // don't make user set LOGTEST_INTEGRATION_TEST_progname or (worse) // LOGTEST_PROJECT_foo_TEST_bar -- only LOGTEST_progname or LOGTEST_bar auto _TEST_ = basename.find("_TEST_"); @@ -558,7 +558,7 @@ int main(int argc, char **argv) { basename.erase(0, _TEST_+6); } - std::string LOGTEST_prog_key{ "LOGTEST_" + basename }; + std::string LOGTEST_prog_key("LOGTEST_" + basename); const char* LOGTEST_prog = getenv(LOGTEST_prog_key.c_str()); // std::cout << LOGTEST_prog_key << "='" << (LOGTEST_prog? LOGTEST_prog : "") << "'" << std::endl; if (LOGTEST_prog && *LOGTEST_prog) -- cgit v1.2.3 From fbeff6d8052d4b614a0a2c8ebaf35b45379ab578 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 28 Jun 2024 07:54:46 -0400 Subject: Give our fsyspath an operator std::string() conversion method. This is redundant (but harmless) on a Posix system, but it fills a missing puzzle piece on Windows. The point of fsyspath is to be able to interchange freely between fsyspath and std::string. Existing fsyspath could be constructed and assigned from std::string, and we could explicitly call its string() method to get a std::string, but an implicit fsyspath-to-string conversion that worked on Posix would trip us up on Windows. Fix that. --- indra/llcommon/fsyspath.h | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'indra') diff --git a/indra/llcommon/fsyspath.h b/indra/llcommon/fsyspath.h index aa4e0132bc..3c749d84de 100644 --- a/indra/llcommon/fsyspath.h +++ b/indra/llcommon/fsyspath.h @@ -69,6 +69,11 @@ public: // shadow base-class string() method with UTF-8 aware method std::string string() const { return super::u8string(); } + // On Posix systems, where value_type is already char, this operator + // std::string() method shadows the base class operator string_type() + // method. But on Windows, where value_type is wchar_t, the base class + // doesn't have operator std::string(). Provide it. + operator std::string() const { return string(); } }; #endif /* ! defined(LL_FSYSPATH_H) */ -- cgit v1.2.3 From cfd7d8905d686411a774c47bbfc13f49882b65e6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 28 Jun 2024 08:30:41 -0400 Subject: Work around MSVC limitation: explicitly call fsyspath::string(). --- indra/test/test.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/test/test.cpp b/indra/test/test.cpp index 22f9ccf334..0e863d8084 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -550,7 +550,9 @@ int main(int argc, char **argv) // specific test we want to monitor, and some viewer integration tests are // quite verbose. In addition to noticing plain LOGTEST= (for all tests), // also notice LOGTEST_progname= (for a specific test). - std::string basename(fsyspath(argv[0]).stem()); + // (Why doesn't MSVC notice fsyspath::operator std::string()? + // Why must we explicitly call fsyspath::string()?) + std::string basename(fsyspath(argv[0]).stem().string()); // don't make user set LOGTEST_INTEGRATION_TEST_progname or (worse) // LOGTEST_PROJECT_foo_TEST_bar -- only LOGTEST_progname or LOGTEST_bar auto _TEST_ = basename.find("_TEST_"); -- cgit v1.2.3 From ac56718929044e101dc2c7bfe3ebc5dff565b76d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 28 Jun 2024 13:21:14 -0400 Subject: Add LuaAutorunPath, LuaCommandPath and LuaRequirePath settings. Remove AutorunLuaScriptFile and the LLLUAmanager::runScriptOnLogin() method that checked it. Instead, iterate over LuaAutorunPath directories at viewer startup, iterate over *.lua files in each and implicitly run those. LuaCommandPath and LuaRequirePath are not yet implemented. --- indra/newview/app_settings/settings.xml | 634 +++++++++++++++++--------------- indra/newview/llappviewer.cpp | 19 + indra/newview/llluamanager.cpp | 21 -- indra/newview/llluamanager.h | 2 - indra/newview/llstartup.cpp | 3 - 5 files changed, 350 insertions(+), 329 deletions(-) (limited to 'indra') diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 2463d56eae..cdaf9a47dd 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -346,7 +346,7 @@ Value 0.5 - AudioStreamingMedia + AudioStreamingMedia Comment Enable streaming @@ -1291,7 +1291,7 @@ Type Boolean Value - 0 + 0 CameraPositionSmoothing @@ -1907,17 +1907,17 @@ Value - DebugAvatarRezTime - - Comment - Display times for avatars to resolve. - Persist - 1 - Type - Boolean - Value - 0 - + DebugAvatarRezTime + + Comment + Display times for avatars to resolve. + Persist + 1 + Type + Boolean + Value + 0 + DebugAvatarLocalTexLoadedTime Comment @@ -2292,39 +2292,39 @@ Value 0 - DefaultFemaleAvatar - - Comment - Default Female Avatar - Persist - 1 - Type - String - Value - Female Shape & Outfit - - DefaultLoginLocation - - Comment - Startup destination default (if not specified on command line) - Persist - 0 - Type - String - Value - - - DefaultMaleAvatar - - Comment - Default Male Avatar - Persist - 1 - Type - String - Value - Male Shape & Outfit - + DefaultFemaleAvatar + + Comment + Default Female Avatar + Persist + 1 + Type + String + Value + Female Shape & Outfit + + DefaultLoginLocation + + Comment + Startup destination default (if not specified on command line) + Persist + 0 + Type + String + Value + + + DefaultMaleAvatar + + Comment + Default Male Avatar + Persist + 1 + Type + String + Value + Male Shape & Outfit + DestinationGuideURL Comment @@ -2699,7 +2699,7 @@ Value 0 - FirstSelectedEnabledPopups + FirstSelectedEnabledPopups Comment Return false if there is not enable popup selected in the list of floater preferences popups @@ -3521,39 +3521,39 @@ Value 0 - InventoryLinking - - Comment - Enable ability to create links to folders and items via "Paste as link". - Persist - 1 - Type - Boolean - Value - 1 - - InventoryOutboxLogging - - Comment - Enable debug output associated with the Merchant Outbox. - Persist - 1 - Type - Boolean - Value - 0 - - InventoryOutboxMakeVisible - - Comment - Enable making the Merchant Outbox and Inbox visible in the inventory for debug purposes. - Persist - 1 - Type - Boolean - Value - 0 - + InventoryLinking + + Comment + Enable ability to create links to folders and items via "Paste as link". + Persist + 1 + Type + Boolean + Value + 1 + + InventoryOutboxLogging + + Comment + Enable debug output associated with the Merchant Outbox. + Persist + 1 + Type + Boolean + Value + 0 + + InventoryOutboxMakeVisible + + Comment + Enable making the Merchant Outbox and Inbox visible in the inventory for debug purposes. + Persist + 1 + Type + Boolean + Value + 0 + InventoryOutboxMaxFolderCount Comment @@ -3807,8 +3807,8 @@ Value 0.25 - Jpeg2000AdvancedCompression - + Jpeg2000AdvancedCompression + Comment Use advanced Jpeg2000 compression options (precincts, blocks, ordering, markers) Persist @@ -3817,9 +3817,9 @@ Boolean Value 0 - - Jpeg2000PrecinctsSize - + + Jpeg2000PrecinctsSize + Comment Size of image precincts. Assumed square and same for all levels. Must be power of 2. Persist @@ -3828,9 +3828,9 @@ S32 Value 256 - - Jpeg2000BlocksSize - + + Jpeg2000BlocksSize + Comment Size of encoding blocks. Assumed square and same for all levels. Must be power of 2. Max 64, Min 4. Persist @@ -3839,7 +3839,7 @@ S32 Value 64 - + KeepAspectForSnapshot Comment @@ -3917,10 +3917,23 @@ Value Monospace + LuaAutorunPath + + Comment + Directories containing scripts to autorun at viewer startup + Persist + 1 + Type + LLSD + Value + + scripts/lua/auto + + LuaChunk Comment - Zero or more Lua chunks to run + Zero or more Lua chunks to run from command line Persist 0 Type @@ -3928,10 +3941,36 @@ Value + LuaCommandPath + + Comment + Directories containing scripts recognized as chat slash commands + Persist + 1 + Type + LLSD + Value + + scripts/lua + + + LuaRequirePath + + Comment + Directories containing Lua modules loadable by require() + Persist + 1 + Type + LLSD + Value + + scripts/lua/require + + LuaScript Comment - Zero or more Lua script files to run + Zero or more Lua script files to run from command line Persist 0 Type @@ -4469,17 +4508,17 @@ Value - MarketplaceListingsLogging - - Comment - Enable debug output associated with the Marketplace Listings (SLM) API. - Persist - 1 - Type - Boolean - Value - 0 - + MarketplaceListingsLogging + + Comment + Enable debug output associated with the Marketplace Listings (SLM) API. + Persist + 1 + Type + Boolean + Value + 0 + MarketplaceURL Comment @@ -5365,28 +5404,28 @@ Value 1000 - FakeInitialOutfitName - - Comment - Pretend that this is first time login and specified name was chosen - Persist - 1 - Type + FakeInitialOutfitName + + Comment + Pretend that this is first time login and specified name was chosen + Persist + 1 + Type String Value - - MyOutfitsAutofill - - Comment - Always autofill My Outfits from library when empty (else happens just once). - Persist - 1 - Type - Boolean - Value - 0 - + + MyOutfitsAutofill + + Comment + Always autofill My Outfits from library when empty (else happens just once). + Persist + 1 + Type + Boolean + Value + 0 + NearMeRange Comment @@ -5506,7 +5545,7 @@ Type Boolean Value - 0 + 0 NonvisibleObjectsInMemoryTime @@ -5517,7 +5556,7 @@ Type U32 Value - 64 + 64 NoPreload @@ -6429,7 +6468,7 @@ Value 6.0 - PreferredMaturity + PreferredMaturity Comment Setting for the user's preferred maturity level (consts in indra_constants.h) @@ -6438,7 +6477,7 @@ Type U32 Value - 13 + 13 PreviewAmbientColor @@ -6608,8 +6647,8 @@ PrimMediaMasterEnabled - - Comment + + Comment Whether or not Media on a Prim is enabled. Persist 1 @@ -6618,9 +6657,9 @@ Value 1 - PrimMediaControlsUseHoverControlSet - - Comment + PrimMediaControlsUseHoverControlSet + + Comment Whether or not hovering over prim media uses minimal "hover" controls or the authored control set. Persist 1 @@ -6629,17 +6668,17 @@ Value 0 - PrimMediaDragNDrop - - Comment - Enable drag and drop of URLs onto prim faces - Persist - 1 - Type - Boolean - Value - 1 - + PrimMediaDragNDrop + + Comment + Enable drag and drop of URLs onto prim faces + Persist + 1 + Type + Boolean + Value + 1 + PrimMediaMaxRetries Comment @@ -6673,7 +6712,7 @@ Value 5.0 - PrimMediaMaxSortedQueueSize + PrimMediaMaxSortedQueueSize Comment Maximum number of objects the viewer will load media for initially @@ -6684,7 +6723,7 @@ Value 100000 - PrimMediaMaxRoundRobinQueueSize + PrimMediaMaxRoundRobinQueueSize Comment Maximum number of objects the viewer will continuously update media for @@ -8788,17 +8827,17 @@ Value 1024 - RenderHeroProbeDistance - - Comment - Distance in meters for hero probes to render out to. - Persist - 1 - Type - F32 - Value - 8 - + RenderHeroProbeDistance + + Comment + Distance in meters for hero probes to render out to. + Persist + 1 + Type + F32 + Value + 8 + RenderHeroProbeUpdateRate Comment @@ -9363,17 +9402,17 @@ Value 1 - RenderTransparentWater - - Comment - Render water as transparent. Setting to false renders water as opaque with a simple texture applied. + RenderTransparentWater + + Comment + Render water as transparent. Setting to false renders water as opaque with a simple texture applied. Persist 1 Type Boolean Value 1 - + RenderTreeLODFactor Comment @@ -9655,18 +9694,18 @@ Value 1 - RenderPreferStreamDraw - - Comment - Use GL_STREAM_DRAW in place of GL_DYNAMIC_DRAW - Persist - 1 - Type - Boolean - Value - 0 - - RenderVolumeLODFactor + RenderPreferStreamDraw + + Comment + Use GL_STREAM_DRAW in place of GL_DYNAMIC_DRAW + Persist + 1 + Type + Boolean + Value + 0 + + RenderVolumeLODFactor Comment Controls level of detail of primitives (multiplier for current screen area when calculated level of detail) @@ -9765,18 +9804,18 @@ Value 0 - ReportBugURL - - Comment - URL used for filing bugs from viewer - Persist - 1 - Type - String - Value - https://feedback.secondlife.com/ - - RevokePermsOnStopAnimation + ReportBugURL + + Comment + URL used for filing bugs from viewer + Persist + 1 + Type + String + Value + https://feedback.secondlife.com/ + + RevokePermsOnStopAnimation Comment Clear animation permssions when choosing "Stop Animating Me" @@ -10029,39 +10068,39 @@ Value 400.0 - SceneLoadingMonitorEnabled - - Comment - Enabled scene loading monitor if set - Persist - 0 - Type - Boolean - Value - 0 - - SceneLoadingMonitorSampleTime - - Comment - Time between screen samples when monitor scene load (seconds) - Persist - 1 - Type - F32 - Value - 0.25 - - SceneLoadingMonitorPixelDiffThreshold - - Comment - Amount of pixels changed required to consider the scene as still loading (square root of fraction of pixels on screen) - Persist - 1 - Type - F32 - Value - 0.02 - + SceneLoadingMonitorEnabled + + Comment + Enabled scene loading monitor if set + Persist + 0 + Type + Boolean + Value + 0 + + SceneLoadingMonitorSampleTime + + Comment + Time between screen samples when monitor scene load (seconds) + Persist + 1 + Type + F32 + Value + 0.25 + + SceneLoadingMonitorPixelDiffThreshold + + Comment + Amount of pixels changed required to consider the scene as still loading (square root of fraction of pixels on screen) + Persist + 1 + Type + F32 + Value + 0.02 + ScriptHelpFollowsCursor Comment @@ -10238,7 +10277,7 @@ Value 0 - AvatarNameTagMode + AvatarNameTagMode Comment Select Avatar Name Tag Mode @@ -10447,7 +10486,7 @@ Value 1 - ShowScriptErrors + ShowScriptErrors Comment Show script errors @@ -10458,7 +10497,7 @@ Value 1 - ShowScriptErrorsLocation + ShowScriptErrorsLocation Comment Show script error in chat (0) or window (1). @@ -10717,8 +10756,8 @@ Display results of find events that are flagged as moderate Persist 1 - HideFromEditor - 1 + HideFromEditor + 1 Type Boolean Value @@ -10730,8 +10769,8 @@ Display results of find events that are flagged as adult Persist 1 - HideFromEditor - 1 + HideFromEditor + 1 Type Boolean Value @@ -10743,8 +10782,8 @@ Display results of find land sales that are flagged as general Persist 1 - HideFromEditor - 1 + HideFromEditor + 1 Type Boolean Value @@ -10756,8 +10795,8 @@ Display results of find land sales that are flagged as moderate Persist 1 - HideFromEditor - 1 + HideFromEditor + 1 Type Boolean Value @@ -10769,8 +10808,8 @@ Display results of find land sales that are flagged as adult Persist 1 - HideFromEditor - 1 + HideFromEditor + 1 Type Boolean Value @@ -10782,8 +10821,8 @@ Display results of find places or find popular that are in general sims Persist 1 - HideFromEditor - 1 + HideFromEditor + 1 Type Boolean Value @@ -10795,8 +10834,8 @@ Display results of find places or find popular that are in moderate sims Persist 1 - HideFromEditor - 1 + HideFromEditor + 1 Type Boolean Value @@ -10808,8 +10847,8 @@ Display results of find places or find popular that are in adult sims Persist 1 - HideFromEditor - 1 + HideFromEditor + 1 Type Boolean Value @@ -10947,17 +10986,17 @@ Value 0 - ShowTutorial - - Comment - Show tutorial window on login - Persist - 1 - Type - Boolean - Value - 0 - + ShowTutorial + + Comment + Show tutorial window on login + Persist + 1 + Type + Boolean + Value + 0 + ShowVoiceVisualizersInCalls Comment @@ -13529,17 +13568,17 @@ Value 0.40000000596 - moapbeacon - - Comment - Beacon / Highlight media on a prim sources - Persist - 1 - Type - Boolean - Value - 0 - + moapbeacon + + Comment + Beacon / Highlight media on a prim sources + Persist + 1 + Type + Boolean + Value + 0 + particlesbeacon Comment @@ -13639,17 +13678,17 @@ Value 0 - SLURLDragNDrop - - Comment - Enable drag and drop of SLURLs onto the viewer - Persist - 1 - Type - Boolean - Value - 1 - + SLURLDragNDrop + + Comment + Enable drag and drop of SLURLs onto the viewer + Persist + 1 + Type + Boolean + Value + 1 + SLURLPassToOtherInstance Comment @@ -13837,10 +13876,10 @@ LLSD Value - snapshot - postcard - mini_map - beacons + snapshot + postcard + mini_map + beacons LandmarksSortedByDate @@ -14590,7 +14629,7 @@ Value 0 - LocalTerrainAsset1 + LocalTerrainAsset1 Comment If set to a non-null UUID, overrides the terrain asset locally for all regions with material assets. Local terrain assets are not visible to others. Please keep in mind that this debug setting may be temporary. Do not rely on this setting existing in future viewer builds. @@ -14601,7 +14640,7 @@ Value 00000000-0000-0000-0000-000000000000 - LocalTerrainAsset2 + LocalTerrainAsset2 Comment If set to a non-null UUID, overrides the terrain asset locally for all regions with material assets. Local terrain assets are not visible to others. Please keep in mind that this debug setting may be temporary. Do not rely on this setting existing in future viewer builds. @@ -14612,7 +14651,7 @@ Value 00000000-0000-0000-0000-000000000000 - LocalTerrainAsset3 + LocalTerrainAsset3 Comment If set to a non-null UUID, overrides the terrain asset locally for all regions with material assets. Local terrain assets are not visible to others. Please keep in mind that this debug setting may be temporary. Do not rely on this setting existing in future viewer builds. @@ -14623,7 +14662,7 @@ Value 00000000-0000-0000-0000-000000000000 - LocalTerrainAsset4 + LocalTerrainAsset4 Comment If set to a non-null UUID, overrides the terrain asset locally for all regions with material assets. Local terrain assets are not visible to others. Please keep in mind that this debug setting may be temporary. Do not rely on this setting existing in future viewer builds. @@ -14634,7 +14673,7 @@ Value 00000000-0000-0000-0000-000000000000 - PathfindingRetrieveNeighboringRegion + PathfindingRetrieveNeighboringRegion Comment Download a neighboring region when visualizing a pathfinding navmesh (default val 99 means do not download neighbors). @@ -14643,9 +14682,9 @@ Type U32 Value - 99 + 99 - PathfindingNavMeshClear + PathfindingNavMeshClear Comment Background color when displaying pathfinding navmesh. @@ -14805,7 +14844,7 @@ 1.0 - PathfindingTestPathValidEndColor + PathfindingTestPathValidEndColor Comment Color of the pathfinding test-pathing tool end-point when the path is valid. @@ -14837,7 +14876,7 @@ 1.0 - PathfindingTestPathColor + PathfindingTestPathColor Comment Color of the pathfinding test-path when the path is valid. @@ -15397,17 +15436,6 @@ Value 3 - AutorunLuaScriptName - - Comment - Script name to autorun after login. - Persist - 1 - Type - String - Value - default.lua - ResetUIScaleOnFirstRun Comment @@ -15485,17 +15513,17 @@ Value 300 - StatsReportFileInterval - - Comment - Interval to save viewer stats file data - Persist - 1 - Type - F32 - Value - 0.2 - + StatsReportFileInterval + + Comment + Interval to save viewer stats file data + Persist + 1 + Type + F32 + Value + 0.2 + StatsReportSkipZeroDataSaves Comment diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 65e5e4f783..ef12fe0bd3 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -117,6 +117,7 @@ #include "lldiriterator.h" #include "llexperiencecache.h" #include "llimagej2c.h" +#include "llluamanager.h" #include "llmemory.h" #include "llprimitive.h" #include "llurlaction.h" @@ -1218,6 +1219,24 @@ bool LLAppViewer::init() // no completion callback: we don't need to know LLLUAmanager::runScriptFile(script); }); + processComposeSwitch( + "LuaAutorunPath", "LuaAutorunPath", + [](const LLSD& directory) + { + // each directory can be relative to the viewer's install + // directory -- if directory is already absolute, operator/() + // preserves it + auto abspath(fsyspath(gDirUtilp->getAppRODataDir()) / directory.asString()); + std::string absdir(abspath.string()); + LL_DEBUGS("InitInfo") << "LuaAutorunPath: " << absdir << LL_ENDL; + LLDirIterator scripts(absdir, "*.lua"); + std::string script; + while (scripts.next(script)) + { + LL_DEBUGS("InitInfo") << "LuaAutorunPath: " << absdir << ": " << script << LL_ENDL; + LLLUAmanager::runScriptFile((abspath / script).string()); + } + }); if (gSavedSettings.getBOOL("QAMode") && gSavedSettings.getS32("QAModeEventHostPort") > 0) { diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 3ed72c34f3..77f197b2d2 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -265,27 +265,6 @@ void LLLUAmanager::runScriptLine(LuaState& L, const std::string& chunk, script_r }); } -void LLLUAmanager::runScriptOnLogin() -{ -#ifndef LL_TEST - std::string filename = gSavedSettings.getString("AutorunLuaScriptName"); - if (filename.empty()) - { - LL_INFOS() << "Script name wasn't set." << LL_ENDL; - return; - } - - filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename); - if (!gDirUtilp->fileExists(filename)) - { - LL_INFOS() << filename << " was not found." << LL_ENDL; - return; - } - - runScriptFile(filename); -#endif // ! LL_TEST -} - std::string read_file(const std::string &name) { llifstream in_file; diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h index af9dcf70c2..dcbb91f799 100644 --- a/indra/newview/llluamanager.h +++ b/indra/newview/llluamanager.h @@ -82,8 +82,6 @@ public: // The return value is the (count, result) pair described above. static std::pair waitScriptLine(LuaState& L, const std::string& chunk); - static void runScriptOnLogin(); - static const std::map getScriptNames() { return sScriptNames; } private: diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 470e512694..3cf0def66e 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -208,7 +208,6 @@ #include "llstacktrace.h" #include "threadpool.h" -#include "llluamanager.h" #include "llperfstats.h" @@ -2422,8 +2421,6 @@ bool idle_startup() LLPerfStats::StatsRecorder::setAutotuneInit(); - LLLUAmanager::runScriptOnLogin(); - return TRUE; } -- cgit v1.2.3 From 07f0f12bcbe864177a145b074c2739eaf08f2c5c Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Mon, 1 Jul 2024 13:02:49 +0300 Subject: Move error strings to strings.xml; pass wearable type and is_worn flag for outfit items --- indra/newview/llappearancelistener.cpp | 20 +++++++++++++------- indra/newview/llappearancemgr.cpp | 9 +++++---- indra/newview/scripts/lua/test_outfits_list.lua | 8 +++++++- indra/newview/skins/default/xui/en/strings.xml | 4 ++++ 4 files changed, 29 insertions(+), 12 deletions(-) (limited to 'indra') diff --git a/indra/newview/llappearancelistener.cpp b/indra/newview/llappearancelistener.cpp index 11037a0078..3db2c64b78 100644 --- a/indra/newview/llappearancelistener.cpp +++ b/indra/newview/llappearancelistener.cpp @@ -29,8 +29,9 @@ #include "llappearancemgr.h" #include "llinventoryfunctions.h" -#include "stringize.h" +#include "lltransutil.h" #include "llwearableitemslist.h" +#include "stringize.h" LLAppearanceListener::LLAppearanceListener() : LLEventAPI("LLAppearance", @@ -65,7 +66,7 @@ LLAppearanceListener::LLAppearanceListener() &LLAppearanceListener::getOutfitsList); add("getOutfitItems", - "Return the table of items(id and name) inside specified outfit folder", + "Return the table of items with info(id : name, wearable_type, is_worn) inside specified outfit folder", &LLAppearanceListener::getOutfitItems); } @@ -76,19 +77,19 @@ void LLAppearanceListener::wearOutfit(LLSD const &data) LLViewerInventoryCategory* cat = gInventory.getCategory(data["folder_id"].asUUID()); if (!cat) { - response.error(stringize("Couldn't find outfit ", data["folder_id"].asUUID())); + response.error(stringize(LLTrans::getString("OutfitNotFound"), data["folder_id"].asUUID())); return; } if (LLFolderType::lookupIsProtectedType(cat->getPreferredType())) { - response.error(stringize("Can't wear system folder ", data["folder_id"].asUUID())); + response.error(stringize(LLTrans::getString("SystemFolderNotWorn"), data["folder_id"].asUUID())); return; } bool append = data["append"].asBoolean(); bool can_wear = append ? LLAppearanceMgr::instance().getCanAddToCOF(cat->getUUID()) : LLAppearanceMgr::instance().getCanReplaceCOF(cat->getUUID()); if (!can_wear) { - std::string msg = append ? "Can't add to COF outfit " : "Can't replace COF with outfit "; + std::string msg = append ? LLTrans::getString("OutfitNotAdded") : LLTrans::getString("OutfitNotReplaced"); response.error(stringize(msg, std::quoted(cat->getName()), " , id: ", cat->getUUID())); return; } @@ -141,7 +142,7 @@ void LLAppearanceListener::getOutfitItems(LLSD const &data) LLViewerInventoryCategory *cat = gInventory.getCategory(outfit_id); if (!cat || cat->getPreferredType() != LLFolderType::FT_OUTFIT) { - response.error(stringize("Can't find outfit folder with id: ", outfit_id.asString())); + response.error(stringize(LLTrans::getString("OutfitNotFound"), outfit_id.asString())); } LLInventoryModel::cat_array_t cat_array; LLInventoryModel::item_array_t item_array; @@ -151,7 +152,12 @@ void LLAppearanceListener::getOutfitItems(LLSD const &data) LLSD items_data; for (const LLPointer &it : item_array) { - items_data[it->getUUID().asString()] = it->getName(); + LLSD info; + info["name"] = it->getName(); + info["wearable_type"] = LLWearableType::getInstance()->getTypeName(it->isWearableType() ? it->getWearableType() : LLWearableType::WT_NONE); + info["is_worn"] = get_is_item_worn(it); + + items_data[it->getUUID().asString()] = info; } response["items"] = items_data; diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 19b81f5a79..3d13f8afc8 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -49,6 +49,7 @@ #include "lloutfitslist.h" #include "llselectmgr.h" #include "llsidepanelappearance.h" +#include "lltransutil.h" #include "llviewerobjectlist.h" #include "llvoavatar.h" #include "llvoavatarself.h" @@ -2941,14 +2942,14 @@ bool LLAppearanceMgr::wearOutfitByName(const std::string& name, bool append, std bool is_system_folder = LLFolderType::lookupIsProtectedType(cat->getPreferredType()); if (is_system_folder) { - error_msg = stringize("Can't wear system folder ", std::quoted(name)); + error_msg = stringize(LLTrans::getString("SystemFolderNotWorn"), std::quoted(name)); return false; } bool can_wear = append ? getCanAddToCOF(cat->getUUID()) : getCanReplaceCOF(cat->getUUID()); if (!can_wear) { - std::string msg = append ? "Can't add to COF outfit " : "Can't replace COF with outfit "; - error_msg = stringize(msg, std::quoted(name), " , id: ", cat->getUUID()); + std::string msg = append ? LLTrans::getString("OutfitNotAdded") : LLTrans::getString("OutfitNotReplaced"); + error_msg = stringize(msg, std::quoted(name), ", id: ", cat->getUUID()); LL_WARNS() << error_msg << LL_ENDL; return false; } @@ -2956,7 +2957,7 @@ bool LLAppearanceMgr::wearOutfitByName(const std::string& name, bool append, std } else { - error_msg = stringize("Couldn't find outfit ", std::quoted(name)); + error_msg = stringize(LLTrans::getString("OutfitNotFound"), std::quoted(name)); LL_WARNS() << error_msg << LL_ENDL; return false; } diff --git a/indra/newview/scripts/lua/test_outfits_list.lua b/indra/newview/scripts/lua/test_outfits_list.lua index dd5f914402..1011029f34 100644 --- a/indra/newview/scripts/lua/test_outfits_list.lua +++ b/indra/newview/scripts/lua/test_outfits_list.lua @@ -34,7 +34,13 @@ function populate_list() action_data.action = "add_list_element" action_data.ctrl_name = "outfits_list" local outfits = {} - for uuid, name in pairs(DATA_MAP) do + for uuid, info in pairs(DATA_MAP) do + name = {} + if SHOW_OUTFITS then + name = info + else + name = info.name + end table.insert(outfits, {value = uuid, columns={column = "outfit_name", value = name}}) end action_data.value = outfits diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 76a2660dbb..492b29fbc7 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -4028,6 +4028,10 @@ Please check http://status.secondlifegrid.net to see if there is a known problem Delete selected item? There are no items in this outfit + + + + Select an editor by setting the environment variable LL_SCRIPT_EDITOR or the ExternalEditor setting. -- cgit v1.2.3 From 3961bac0ef705775883a4b37f2b6a84e41b82c05 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Mon, 1 Jul 2024 14:47:52 +0300 Subject: build fix --- indra/newview/llappearancelistener.cpp | 5 +++-- indra/newview/llappearancemgr.cpp | 13 +++++++++++-- indra/newview/llappearancemgr.h | 3 ++- 3 files changed, 16 insertions(+), 5 deletions(-) (limited to 'indra') diff --git a/indra/newview/llappearancelistener.cpp b/indra/newview/llappearancelistener.cpp index 3db2c64b78..b29eb48a1b 100644 --- a/indra/newview/llappearancelistener.cpp +++ b/indra/newview/llappearancelistener.cpp @@ -100,7 +100,7 @@ void LLAppearanceListener::wearOutfitByName(LLSD const &data) { Response response(LLSD(), data); std::string error_msg; - if (!LLAppearanceMgr::instance().wearOutfitByName(data["folder_name"].asString(), data["append"].asBoolean(), error_msg)) + if (!LLAppearanceMgr::instance().wearOutfitByName(data["folder_name"].asString(), error_msg, data["append"].asBoolean())) { response.error(error_msg); } @@ -147,7 +147,8 @@ void LLAppearanceListener::getOutfitItems(LLSD const &data) LLInventoryModel::cat_array_t cat_array; LLInventoryModel::item_array_t item_array; - gInventory.collectDescendentsIf(outfit_id, cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH, LLFindOutfitItems()); + LLFindOutfitItems collector = LLFindOutfitItems(); + gInventory.collectDescendentsIf(outfit_id, cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH, collector); LLSD items_data; for (const LLPointer &it : item_array) diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 3d13f8afc8..e4a545a55b 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -2904,7 +2904,7 @@ void LLAppearanceMgr::wearInventoryCategoryOnAvatar( LLInventoryCategory* catego LLAppearanceMgr::changeOutfit(TRUE, category->getUUID(), append); } -bool LLAppearanceMgr::wearOutfitByName(const std::string& name, bool append, std::string& error_msg) +bool LLAppearanceMgr::wearOutfitByName(const std::string& name, std::string& error_msg, bool append) { LL_INFOS("Avatar") << self_av_string() << "Wearing category " << name << LL_ENDL; @@ -2950,7 +2950,6 @@ bool LLAppearanceMgr::wearOutfitByName(const std::string& name, bool append, std { std::string msg = append ? LLTrans::getString("OutfitNotAdded") : LLTrans::getString("OutfitNotReplaced"); error_msg = stringize(msg, std::quoted(name), ", id: ", cat->getUUID()); - LL_WARNS() << error_msg << LL_ENDL; return false; } LLAppearanceMgr::wearInventoryCategory(cat, copy_items, append); @@ -2958,6 +2957,16 @@ bool LLAppearanceMgr::wearOutfitByName(const std::string& name, bool append, std else { error_msg = stringize(LLTrans::getString("OutfitNotFound"), std::quoted(name)); + return false; + } + return true; +} + +bool LLAppearanceMgr::wearOutfitByName(const std::string& name, bool append) +{ + std::string error_msg; + if(!wearOutfitByName(name, error_msg, append)) + { LL_WARNS() << error_msg << LL_ENDL; return false; } diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index d7b6cd5a61..adc783be5a 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -59,7 +59,8 @@ public: void wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append); void wearInventoryCategoryOnAvatar(LLInventoryCategory* category, bool append); void wearCategoryFinal(const LLUUID& cat_id, bool copy_items, bool append); - bool wearOutfitByName(const std::string &name, bool append = false, std::string &error_msg = std::string()); + bool wearOutfitByName(const std::string &name, std::string &error_msg, bool append = false); + bool wearOutfitByName(const std::string &name, bool append = false); void changeOutfit(bool proceed, const LLUUID& category, bool append); void replaceCurrentOutfit(const LLUUID& new_outfit); void renameOutfit(const LLUUID& outfit_id); -- cgit v1.2.3 From 212868a8c3f803b387da602b6440f69c2c617e40 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 2 Jul 2024 10:35:34 -0400 Subject: Promote LuaRemover from llluamanager.cpp to lua_function.h. --- indra/llcommon/lua_function.h | 34 ++++++++++++++++++++++++++++++++-- indra/newview/llluamanager.cpp | 25 ------------------------- 2 files changed, 32 insertions(+), 27 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index c32a586d79..b3b1f40ae5 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -118,11 +118,12 @@ private: * LuaPopper *****************************************************************************/ /** - * LuaPopper is an RAII struct whose role is to pop some number of entries + * LuaPopper is an RAII class whose role is to pop some number of entries * from the Lua stack if the calling function exits early. */ -struct LuaPopper +class LuaPopper { +public: LuaPopper(lua_State* L, int count): mState(L), mCount(count) @@ -136,10 +137,39 @@ struct LuaPopper void disarm() { set(0); } void set(int count) { mCount = count; } +private: lua_State* mState; int mCount; }; +/***************************************************************************** +* LuaRemover +*****************************************************************************/ +/** + * Remove a particular stack index on exit from enclosing scope. + * If you pass a negative index (meaning relative to the current stack top), + * converts to an absolute index. The point of LuaRemover is to remove the + * entry at the specified index regardless of subsequent pushes to the stack. + */ +class LuaRemover +{ +public: + LuaRemover(lua_State* L, int index): + mState(L), + mIndex(lua_absindex(L, index)) + {} + LuaRemover(const LuaRemover&) = delete; + LuaRemover& operator=(const LuaRemover&) = delete; + ~LuaRemover() + { + lua_remove(mState, mIndex); + } + +private: + lua_State* mState; + int mIndex; +}; + /***************************************************************************** * lua_function (and helper class LuaFunction) *****************************************************************************/ diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 77f197b2d2..c95ce9c57b 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -309,31 +309,6 @@ LLRequireResolver::LLRequireResolver(lua_State *L, const std::string& path) : luaL_argerrorL(L, 1, "cannot require a full path"); } -/** - * Remove a particular stack index on exit from enclosing scope. - * If you pass a negative index (meaning relative to the current stack top), - * converts to an absolute index. The point of LuaRemover is to remove the - * entry at the specified index regardless of subsequent pushes to the stack. - */ -class LuaRemover -{ -public: - LuaRemover(lua_State* L, int index): - mState(L), - mIndex(lua_absindex(L, index)) - {} - LuaRemover(const LuaRemover&) = delete; - LuaRemover& operator=(const LuaRemover&) = delete; - ~LuaRemover() - { - lua_remove(mState, mIndex); - } - -private: - lua_State* mState; - int mIndex; -}; - // push the loaded module or throw a Lua error void LLRequireResolver::findModule() { -- cgit v1.2.3 From f8357732a108e579c285a76a53c550b8b5c0a153 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 2 Jul 2024 12:09:59 -0400 Subject: Make require() implementation honor LuaRequirePath setting. Remove LL_TEST special case from require() code (to search in the viewer's source tree). Instead, make llluamanager_test.cpp append to LuaRequirePath to get the same effect. --- indra/newview/llluamanager.cpp | 18 +++++++---------- indra/newview/tests/llluamanager_test.cpp | 33 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 11 deletions(-) (limited to 'indra') diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index c95ce9c57b..ccfa08078e 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -32,6 +32,7 @@ #include "llcoros.h" #include "llerror.h" #include "lleventcoro.h" +#include "llsdutil.h" #include "llviewercontrol.h" #include "lua_function.h" #include "lualistener.h" @@ -338,18 +339,13 @@ void LLRequireResolver::findModule() fail(); } - std::vector lib_paths + LLSD lib_paths(gSavedSettings.getLLSD("LuaRequirePath")); + LL_DEBUGS("Lua") << "LuaRequirePath = " << lib_paths << LL_ENDL; + for (const auto& path : llsd::inArray(lib_paths)) { - gDirUtilp->getExpandedFilename(LL_PATH_SCRIPTS, "lua", "require"), -#ifdef LL_TEST - // Build-time tests don't have the app bundle - use source tree. - fsyspath(__FILE__).parent_path() / "scripts" / "lua" / "require", -#endif - }; - - for (const auto& path : lib_paths) - { - std::string absolutePathOpt = (path / mPathToResolve).u8string(); + // if path is already absolute, operator/() preserves it + auto abspath(fsyspath(gDirUtilp->getAppRODataDir()) / path.asString()); + std::string absolutePathOpt = (abspath / mPathToResolve).u8string(); if (absolutePathOpt.empty()) luaL_error(L, "error requiring module '%s'", mPathToResolve.u8string().data()); diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index d3fc70dfd5..55e87acaea 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -21,6 +21,7 @@ #include "../llcommon/tests/StringVec.h" #include "../test/lltut.h" #include "llapp.h" +#include "llcontrol.h" #include "lldate.h" #include "llevents.h" #include "lleventcoro.h" @@ -39,6 +40,8 @@ public: bool frame() override { return true; } }; +LLControlGroup gSavedSettings("Global"); + template auto listener(CALLABLE&& callable) { @@ -57,6 +60,36 @@ namespace tut { struct llluamanager_data { + llluamanager_data() + { + // Load gSavedSettings from source tree + // indra/newview/tests/llluamanager_test.cpp => + // indra/newview + auto newview{ fsyspath(__FILE__).parent_path().parent_path() }; + auto settings{ newview / "app_settings" / "settings.xml" }; + // true suppresses implicit declare; implicit declare requires + // that every variable in settings.xml has a Comment, which many don't. + gSavedSettings.loadFromFile(settings.u8string().c_str(), true); + // At test time, since we don't have the app bundle available, + // extend LuaRequirePath to include the require directory in the + // source tree. + auto require{ (newview / "scripts" / "lua" / "require").u8string() }; + auto paths{ gSavedSettings.getLLSD("LuaRequirePath") }; + bool found = false; + for (const auto& path : llsd::inArray(paths)) + { + if (path.asString() == require) + { + found = true; + break; + } + } + if (! found) + { + paths.append(require); + gSavedSettings.setLLSD("LuaRequirePath", paths); + } + } // We need an LLApp instance because LLLUAmanager uses coroutines, // which suspend, and when a coroutine suspends it checks LLApp state, // and if it's not APP_STATUS_RUNNING the coroutine terminates. -- cgit v1.2.3 From 9a3c770a3bf430da8878a8691cee9b726a5f026c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 2 Jul 2024 13:12:40 -0400 Subject: Eliminate c_str() calls from LLControlGroup::loadFromFile() calls. Passing std::string::c_str() to a (const std::string&) function parameter is worse than clutter, it's pointless overhead: it forces the compiler to construct a new std::string instance, instead of passing a const reference to the one you already have in hand. --- indra/llxml/tests/llcontrol_test.cpp | 12 ++++++------ indra/newview/tests/llluamanager_test.cpp | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'indra') diff --git a/indra/llxml/tests/llcontrol_test.cpp b/indra/llxml/tests/llcontrol_test.cpp index f5f8b285f7..595c6a600b 100644 --- a/indra/llxml/tests/llcontrol_test.cpp +++ b/indra/llxml/tests/llcontrol_test.cpp @@ -97,7 +97,7 @@ namespace tut template<> template<> void control_group_t::test<1>() { - int results = mCG->loadFromFile(mTestConfigFile.c_str()); + int results = mCG->loadFromFile(mTestConfigFile); ensure("number of settings", (results == 1)); ensure("value of setting", (mCG->getU32("TestSetting") == 12)); } @@ -106,14 +106,14 @@ namespace tut template<> template<> void control_group_t::test<2>() { - int results = mCG->loadFromFile(mTestConfigFile.c_str()); + int results = mCG->loadFromFile(mTestConfigFile); mCG->setU32("TestSetting", 13); ensure_equals("value of changed setting", mCG->getU32("TestSetting"), 13); LLControlGroup test_cg("foo2"); std::string temp_test_file = (mTestConfigDir + "setting_llsd_temp.xml"); mCleanups.push_back(temp_test_file); mCG->saveToFile(temp_test_file.c_str(), TRUE); - results = test_cg.loadFromFile(temp_test_file.c_str()); + results = test_cg.loadFromFile(temp_test_file); ensure("number of changed settings loaded", (results == 1)); ensure("value of changed settings loaded", (test_cg.getU32("TestSetting") == 13)); } @@ -126,7 +126,7 @@ namespace tut // a default settings file that declares variables, rather than a user // settings file. When loadFromFile() encounters an unrecognized user // settings variable, it forcibly preserves it (CHOP-962). - int results = mCG->loadFromFile(mTestConfigFile.c_str(), true); + int results = mCG->loadFromFile(mTestConfigFile, true); LLControlVariable* control = mCG->getControl("TestSetting"); LLSD new_value = 13; control->setValue(new_value, FALSE); @@ -135,7 +135,7 @@ namespace tut std::string temp_test_file = (mTestConfigDir + "setting_llsd_persist_temp.xml"); mCleanups.push_back(temp_test_file); mCG->saveToFile(temp_test_file.c_str(), TRUE); - results = test_cg.loadFromFile(temp_test_file.c_str()); + results = test_cg.loadFromFile(temp_test_file); //If we haven't changed any settings, then we shouldn't have any settings to load ensure("number of non-persisted changed settings loaded", (results == 0)); } @@ -144,7 +144,7 @@ namespace tut template<> template<> void control_group_t::test<4>() { - int results = mCG->loadFromFile(mTestConfigFile.c_str()); + int results = mCG->loadFromFile(mTestConfigFile); ensure("number of settings", (results == 1)); mCG->getControl("TestSetting")->getSignal()->connect(boost::bind(&this->handleListenerTest)); mCG->setU32("TestSetting", 13); diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index 55e87acaea..824ddd445b 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -69,7 +69,7 @@ namespace tut auto settings{ newview / "app_settings" / "settings.xml" }; // true suppresses implicit declare; implicit declare requires // that every variable in settings.xml has a Comment, which many don't. - gSavedSettings.loadFromFile(settings.u8string().c_str(), true); + gSavedSettings.loadFromFile(settings.u8string(), true); // At test time, since we don't have the app bundle available, // extend LuaRequirePath to include the require directory in the // source tree. -- cgit v1.2.3 From 66fb45ddc7dd1a994f6b8312687cb73dbb1281dd Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 3 Jul 2024 14:03:56 -0400 Subject: Add llsd::toArray() and llsd::toMap() utility functions. These encapsulate looping over a C++ iterable (be it a sequence container or an associative container) and returning an LLSD array or map, respectively, derived from the C++ container. By default, each C++ container item is directly converted to LLSD. Also make LLSDParam slightly more efficient by using std::vector::emplace_back() instead of push_back(), which supports std::vector, so we need not use std::shared_ptr. --- indra/llcommon/llsdutil.h | 73 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 9 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index aa497c53c7..7c31dc8aa0 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -365,15 +365,14 @@ private: // subject function has returned, so we must ensure that any constructed // LLSDParam lives just as long as this LLSDParam does. Putting // each LLSDParam on the heap and capturing a smart pointer in a vector - // works. We would have liked to use std::unique_ptr, but vector entries - // must be copyable. + // works. // (Alternatively we could assume that every instance of LLSDParam // will be asked for at most ONE conversion. We could store a scalar // std::unique_ptr and, when constructing an new LLSDParam, assert that // the unique_ptr is empty. But some future change in usage patterns, and // consequent failure of that assertion, would be very mysterious. Instead // of explaining how to fix it, just fix it now.) - mutable std::vector> converters_; + mutable std::vector> converters_; public: LLSDParam(const LLSD& value): value_(value) {} @@ -389,9 +388,9 @@ public: { // capture 'ptr' with the specific subclass type because converters_ // only stores LLSDParamBase pointers - auto ptr{ std::make_shared>>(value_) }; + auto ptr{ new LLSDParam>(value_) }; // keep the new converter alive until we ourselves are destroyed - converters_.push_back(ptr); + converters_.emplace_back(ptr); return *ptr; } }; @@ -474,12 +473,12 @@ public: } }; -namespace llsd -{ - /***************************************************************************** * range-based for-loop helpers for LLSD *****************************************************************************/ +namespace llsd +{ + /// Usage: for (LLSD item : inArray(someLLSDarray)) { ... } class inArray { @@ -525,7 +524,9 @@ private: } // namespace llsd - +/***************************************************************************** +* deep and shallow clone +*****************************************************************************/ // Creates a deep clone of an LLSD object. Maps, Arrays and binary objects // are duplicated, atomic primitives (Boolean, Integer, Real, etc) simply // use a shared reference. @@ -553,6 +554,60 @@ LLSD shallow(LLSD value, LLSD filter=LLSD()) { return llsd_shallow(value, filter } // namespace llsd +/***************************************************************************** +* toArray(), toMap() +*****************************************************************************/ +namespace llsd +{ + +// For some T convertible to LLSD, given std::vector myVec, +// toArray(myVec) returns an LLSD array whose entries correspond to the +// items in myVec. +// For some U convertible to LLSD, given function U xform(const T&), +// toArray(myVec, xform) returns an LLSD array whose every entry is +// xform(item) of the corresponding item in myVec. +// toArray() actually works with any container usable with range +// 'for', not just std::vector. +// (Once we get C++20 we can use std::identity instead of this default lambda.) +template +LLSD toArray(const C& container, FUNC&& func=[](const auto& arg){ return arg; }) +{ + LLSD array; + for (const auto& item : container) + { + array.append(std::forward(func)(item)); + } + return array; +} + +// For some T convertible to LLSD, given std::map myMap, +// toMap(myMap) returns an LLSD map whose entries correspond to the +// (key, value) pairs in myMap. +// For some U convertible to LLSD, given function +// std::pair xform(const std::pair&), +// toMap(myMap, xform) returns an LLSD map whose every entry is +// xform(pair) of the corresponding (key, value) pair in myMap. +// toMap() actually works with any container usable with range 'for', not +// just std::map. It need not even be an associative container, as long as +// you pass an xform function that returns std::pair. +// (Once we get C++20 we can use std::identity instead of this default lambda.) +template +LLSD toMap(const C& container, FUNC&& func=[](const auto& arg){ return arg; }) +{ + LLSD map; + for (const auto& pair : container) + { + const auto& [key, value] = std::forward(func)(pair); + map[key] = value; + } + return map; +} + +} // namespace llsd + +/***************************************************************************** +* boost::hash +*****************************************************************************/ // Specialization for generating a hash value from an LLSD block. namespace boost { -- cgit v1.2.3 From a877e3a0994a19d522e77d6781844341197dd6dc Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 3 Jul 2024 14:05:25 -0400 Subject: Use llsd::toMap() to return LLSD maps from "LLAppearance" listener. --- indra/newview/llappearancelistener.cpp | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) (limited to 'indra') diff --git a/indra/newview/llappearancelistener.cpp b/indra/newview/llappearancelistener.cpp index b29eb48a1b..2a3133433a 100644 --- a/indra/newview/llappearancelistener.cpp +++ b/indra/newview/llappearancelistener.cpp @@ -127,12 +127,9 @@ void LLAppearanceListener::getOutfitsList(LLSD const &data) LLIsType is_category(LLAssetType::AT_CATEGORY); gInventory.collectDescendentsIf(outfits_id, cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH, is_category); - LLSD outfits_data; - for (const LLPointer &cat : cat_array) - { - outfits_data[cat->getUUID().asString()] = cat->getName(); - } - response["outfits"] = outfits_data; + response["outfits"] = llsd::toMap(cat_array, + [](const LLPointer &cat) + { return std::make_pair(cat->getUUID().asString(), cat->getName()); }); } void LLAppearanceListener::getOutfitItems(LLSD const &data) @@ -150,16 +147,14 @@ void LLAppearanceListener::getOutfitItems(LLSD const &data) LLFindOutfitItems collector = LLFindOutfitItems(); gInventory.collectDescendentsIf(outfit_id, cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH, collector); - LLSD items_data; - for (const LLPointer &it : item_array) - { - LLSD info; - info["name"] = it->getName(); - info["wearable_type"] = LLWearableType::getInstance()->getTypeName(it->isWearableType() ? it->getWearableType() : LLWearableType::WT_NONE); - info["is_worn"] = get_is_item_worn(it); - - items_data[it->getUUID().asString()] = info; - } - - response["items"] = items_data; + response["items"] = llsd::toMap(item_array, + [](const LLPointer &it) + { + return std::make_pair( + it->getUUID().asString(), + llsd::map( + "name", it->getName(), + "wearable_type", LLWearableType::getInstance()->getTypeName(it->isWearableType() ? it->getWearableType() : LLWearableType::WT_NONE), + "is_worn", get_is_item_worn(it))); + }); } -- cgit v1.2.3 From ece0f4eb566af937d724f60f934beb6dfcb4d493 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 5 Jul 2024 16:03:51 +0300 Subject: clean up and rename demo script --- indra/newview/llappearancelistener.cpp | 93 +++++++++-------- indra/newview/llappearancelistener.h | 5 +- indra/newview/llappearancemgr.cpp | 28 ++++- indra/newview/llappearancemgr.h | 1 + indra/newview/llwearableitemslist.h | 3 +- indra/newview/scripts/lua/require/LLAppearance.lua | 28 ++--- indra/newview/scripts/lua/test_LLAppearance.lua | 114 +++++++++++++++++++++ indra/newview/scripts/lua/test_outfits_list.lua | 114 --------------------- 8 files changed, 205 insertions(+), 181 deletions(-) create mode 100644 indra/newview/scripts/lua/test_LLAppearance.lua delete mode 100644 indra/newview/scripts/lua/test_outfits_list.lua (limited to 'indra') diff --git a/indra/newview/llappearancelistener.cpp b/indra/newview/llappearancelistener.cpp index 2a3133433a..75eaf29186 100644 --- a/indra/newview/llappearancelistener.cpp +++ b/indra/newview/llappearancelistener.cpp @@ -38,28 +38,20 @@ LLAppearanceListener::LLAppearanceListener() "API to wear a specified outfit and wear/remove individual items") { add("wearOutfit", - "Wear outfit by folder id: [folder_id]" + "Wear outfit by folder id: [folder_id] OR by folder name: [folder_name]\n" "When [\"append\"] is true, outfit will be added to COF\n" "otherwise it will replace current oufit", - &LLAppearanceListener::wearOutfit, - llsd::map("folder_id", LLSD(), "append", LLSD())); + &LLAppearanceListener::wearOutfit); - add("wearOutfitByName", - "Wear outfit by folder name: [folder_name]" - "When [\"append\"] is true, outfit will be added to COF\n" - "otherwise it will replace current oufit", - &LLAppearanceListener::wearOutfitByName, - llsd::map("folder_name", LLSD(), "append", LLSD())); - - add("wearItem", - "Wear item by item id: [item_id]", - &LLAppearanceListener::wearItem, - llsd::map("item_id", LLSD(), "replace", LLSD())); + add("wearItems", + "Wear items by id: [items_id]", + &LLAppearanceListener::wearItems, + llsd::map("items_id", LLSD(), "replace", LLSD())); - add("detachItem", - "Detach item by item id: [item_id]", - &LLAppearanceListener::detachItem, - llsd::map("item_id", LLSD())); + add("detachItems", + "Detach items by id: [items_id]", + &LLAppearanceListener::detachItems, + llsd::map("items_id", LLSD())); add("getOutfitsList", "Return the table with Outfits info(id and name)", @@ -74,46 +66,61 @@ LLAppearanceListener::LLAppearanceListener() void LLAppearanceListener::wearOutfit(LLSD const &data) { Response response(LLSD(), data); - LLViewerInventoryCategory* cat = gInventory.getCategory(data["folder_id"].asUUID()); - if (!cat) + if (!data.has("folder_id") && !data.has("folder_name")) { - response.error(stringize(LLTrans::getString("OutfitNotFound"), data["folder_id"].asUUID())); - return; + return response.error("Either [folder_id] or [folder_name] is required"); } - if (LLFolderType::lookupIsProtectedType(cat->getPreferredType())) + + std::string error_msg; + bool result(false); + bool append = data.has("append") ? data["append"].asBoolean() : false; + if (data.has("folder_id")) { - response.error(stringize(LLTrans::getString("SystemFolderNotWorn"), data["folder_id"].asUUID())); - return; + result = LLAppearanceMgr::instance().wearOutfit(data["folder_id"].asUUID(), error_msg, append); } - bool append = data["append"].asBoolean(); - bool can_wear = append ? LLAppearanceMgr::instance().getCanAddToCOF(cat->getUUID()) : LLAppearanceMgr::instance().getCanReplaceCOF(cat->getUUID()); - if (!can_wear) + else { - std::string msg = append ? LLTrans::getString("OutfitNotAdded") : LLTrans::getString("OutfitNotReplaced"); - response.error(stringize(msg, std::quoted(cat->getName()), " , id: ", cat->getUUID())); - return; + result = LLAppearanceMgr::instance().wearOutfitByName(data["folder_name"].asString(), error_msg, append); } - LLAppearanceMgr::instance().wearInventoryCategory(cat, false, append); -} -void LLAppearanceListener::wearOutfitByName(LLSD const &data) -{ - Response response(LLSD(), data); - std::string error_msg; - if (!LLAppearanceMgr::instance().wearOutfitByName(data["folder_name"].asString(), error_msg, data["append"].asBoolean())) + if (!result) { response.error(error_msg); } } -void LLAppearanceListener::wearItem(LLSD const &data) +void LLAppearanceListener::wearItems(LLSD const &data) { - LLAppearanceMgr::instance().wearItemOnAvatar(data["item_id"].asUUID(), true, data["replace"].asBoolean()); + if (data["items_id"].isArray()) + { + uuid_vec_t ids; + for (const auto &id : llsd::inArray(data["items_id"])) + { + ids.push_back(id); + } + LLAppearanceMgr::instance().wearItemsOnAvatar(ids, true, data["replace"].asBoolean()); + } + else + { + LLAppearanceMgr::instance().wearItemOnAvatar(data["items_id"].asUUID(), true, data["replace"].asBoolean()); + } } -void LLAppearanceListener::detachItem(LLSD const &data) +void LLAppearanceListener::detachItems(LLSD const &data) { - LLAppearanceMgr::instance().removeItemFromAvatar(data["item_id"].asUUID()); + if (data["items_id"].isArray()) + { + uuid_vec_t ids; + for (const auto &id : llsd::inArray(data["items_id"])) + { + ids.push_back(id); + } + LLAppearanceMgr::instance().removeItemsFromAvatar(ids); + } + else + { + LLAppearanceMgr::instance().removeItemFromAvatar(data["items_id"].asUUID()); + } } void LLAppearanceListener::getOutfitsList(LLSD const &data) @@ -139,7 +146,7 @@ void LLAppearanceListener::getOutfitItems(LLSD const &data) LLViewerInventoryCategory *cat = gInventory.getCategory(outfit_id); if (!cat || cat->getPreferredType() != LLFolderType::FT_OUTFIT) { - response.error(stringize(LLTrans::getString("OutfitNotFound"), outfit_id.asString())); + return response.error(stringize(LLTrans::getString("OutfitNotFound"), outfit_id.asString())); } LLInventoryModel::cat_array_t cat_array; LLInventoryModel::item_array_t item_array; diff --git a/indra/newview/llappearancelistener.h b/indra/newview/llappearancelistener.h index 00553c072f..04c5eac2eb 100644 --- a/indra/newview/llappearancelistener.h +++ b/indra/newview/llappearancelistener.h @@ -36,9 +36,8 @@ public: private: void wearOutfit(LLSD const &data); - void wearOutfitByName(LLSD const &data); - void wearItem(LLSD const &data); - void detachItem(LLSD const &data); + void wearItems(LLSD const &data); + void detachItems(LLSD const &data); void getOutfitsList(LLSD const &data); void getOutfitItems(LLSD const &data); }; diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index e4a545a55b..7a34006323 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -2939,8 +2939,8 @@ bool LLAppearanceMgr::wearOutfitByName(const std::string& name, std::string& err if(cat) { - bool is_system_folder = LLFolderType::lookupIsProtectedType(cat->getPreferredType()); - if (is_system_folder) + // don't allow wearing a system folder + if (LLFolderType::lookupIsProtectedType(cat->getPreferredType())) { error_msg = stringize(LLTrans::getString("SystemFolderNotWorn"), std::quoted(name)); return false; @@ -2962,6 +2962,30 @@ bool LLAppearanceMgr::wearOutfitByName(const std::string& name, std::string& err return true; } +bool LLAppearanceMgr::wearOutfit(const LLUUID &cat_id, std::string &error_msg, bool append) +{ + LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); + if (!cat) + { + error_msg = stringize(LLTrans::getString("OutfitNotFound"), cat_id); + return false; + } + if (LLFolderType::lookupIsProtectedType(cat->getPreferredType())) + { + error_msg = stringize(LLTrans::getString("SystemFolderNotWorn"), cat_id); + return false; + } + bool can_wear = append ? LLAppearanceMgr::instance().getCanAddToCOF(cat_id) : LLAppearanceMgr::instance().getCanReplaceCOF(cat_id); + if (!can_wear) + { + std::string msg = append ? LLTrans::getString("OutfitNotAdded") : LLTrans::getString("OutfitNotReplaced"); + error_msg = stringize(msg, std::quoted(cat->getName()), " , id: ", cat_id); + return false; + } + LLAppearanceMgr::instance().wearInventoryCategory(cat, false, append); + return true; +} + bool LLAppearanceMgr::wearOutfitByName(const std::string& name, bool append) { std::string error_msg; diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index adc783be5a..b795494f94 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -59,6 +59,7 @@ public: void wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append); void wearInventoryCategoryOnAvatar(LLInventoryCategory* category, bool append); void wearCategoryFinal(const LLUUID& cat_id, bool copy_items, bool append); + bool wearOutfit(const LLUUID &cat_id, std::string &error_msg, bool append = false); bool wearOutfitByName(const std::string &name, std::string &error_msg, bool append = false); bool wearOutfitByName(const std::string &name, bool append = false); void changeOutfit(bool proceed, const LLUUID& category, bool append); diff --git a/indra/newview/llwearableitemslist.h b/indra/newview/llwearableitemslist.h index a24679961f..15033f5e9a 100644 --- a/indra/newview/llwearableitemslist.h +++ b/indra/newview/llwearableitemslist.h @@ -506,9 +506,8 @@ protected: LLWearableType::EType mMenuWearableType; }; -class LLFindOutfitItems : public LLInventoryCollectFunctor +struct LLFindOutfitItems : public LLInventoryCollectFunctor { - public: LLFindOutfitItems() {} virtual ~LLFindOutfitItems() {} virtual bool operator()(LLInventoryCategory *cat, LLInventoryItem *item); diff --git a/indra/newview/scripts/lua/require/LLAppearance.lua b/indra/newview/scripts/lua/require/LLAppearance.lua index 165bb6d06f..f533d22daf 100644 --- a/indra/newview/scripts/lua/require/LLAppearance.lua +++ b/indra/newview/scripts/lua/require/LLAppearance.lua @@ -1,29 +1,23 @@ -leap = require 'leap' +local leap = require 'leap' local LLAppearance = {} -function LLAppearance.addOutfit(folder) - leap.request('LLAppearance', {op='wearOutfit', append = true, folder_id=folder}) +function LLAppearance.wearOutfit(folder, action) + action = action or 'add' + leap.request('LLAppearance', {op='wearOutfit', append = (action == 'add'), folder_id=folder}) end -function LLAppearance.replaceOutfit(folder) - leap.request('LLAppearance', {op='wearOutfit', append = false, folder_id=folder}) +function LLAppearance.wearOutfitByName(folder, action) + action = action or 'add' + leap.request('LLAppearance', {op='wearOutfit', append = (action == 'add'), folder_name=folder}) end -function LLAppearance.addOutfitByName(folder) - leap.request('LLAppearance', {op='wearOutfitByName', append = true, folder_name=folder}) +function LLAppearance.wearItems(items_id, replace) + leap.send('LLAppearance', {op='wearItems', replace = replace, items_id=items_id}) end -function LLAppearance.replaceOutfitByName(folder) - leap.request('LLAppearance', {op='wearOutfitByName', append = false, folder_name=folder}) -end - -function LLAppearance.wearItem(item_id, replace) - leap.send('LLAppearance', {op='wearItem', replace = replace, item_id=item_id}) -end - -function LLAppearance.detachItem(item_id) - leap.send('LLAppearance', {op='detachItem', item_id=item_id}) +function LLAppearance.detachItems(items_id) + leap.send('LLAppearance', {op='detachItems', items_id=items_id}) end function LLAppearance.getOutfitsList() diff --git a/indra/newview/scripts/lua/test_LLAppearance.lua b/indra/newview/scripts/lua/test_LLAppearance.lua new file mode 100644 index 0000000000..5ddd9f15ff --- /dev/null +++ b/indra/newview/scripts/lua/test_LLAppearance.lua @@ -0,0 +1,114 @@ +local Floater = require 'Floater' +local LLAppearance = require 'LLAppearance' +local startup = require 'startup' +local inspect = require 'inspect' + +local SHOW_OUTFITS = true +local SELECTED_OUTFIT_ID = {} +local DATA_MAP = {} + +local wearables_lbl = 'Show wearables' +local outfits_lbl = 'Show outfits' +local replace_cof_lbl = 'Replace COF' +local add_cof_lbl = 'Add to COF' +local outfits_title = 'Outfits' +local wear_lbl = 'Wear item' +local detach_lbl = 'Detach item' + +local flt = Floater:new( + "luafloater_outfits_list.xml", + {outfits_list = {"double_click"}}) + +function get_selected_id() + return flt:request({action="get_selected_id", ctrl_name='outfits_list'}).value +end + +function populate_list() + if SHOW_OUTFITS then + DATA_MAP = LLAppearance.getOutfitsList() + else + DATA_MAP = LLAppearance.getOutfitItems(SELECTED_OUTFIT_ID) + end + + local action_data = {} + action_data.action = "add_list_element" + action_data.ctrl_name = "outfits_list" + local outfits = {} + for uuid, info in pairs(DATA_MAP) do + name = {} + if SHOW_OUTFITS then + name = info + else + name = info.name + end + table.insert(outfits, {value = uuid, columns={column = "outfit_name", value = name}}) + end + action_data.value = outfits + flt:post(action_data) +end + +function set_label(btn_name, value) + flt:post({action="set_label", ctrl_name=btn_name, value=value}) +end + +function set_enabled(btn_name, value) + flt:post({action="set_enabled", ctrl_name=btn_name, value=value}) +end + +function update_labels() + if SHOW_OUTFITS then + set_label('select_btn', wearables_lbl) + set_label('replace_btn', replace_cof_lbl) + set_label('add_btn', add_cof_lbl) + + set_enabled('select_btn', false) + flt:post({action="set_title", value=outfits_title}) + else + set_label('select_btn', outfits_lbl) + set_label('replace_btn', wear_lbl) + set_label('add_btn', detach_lbl) + + set_enabled('select_btn', true) + flt:post({action="set_title", value=DATA_MAP[SELECTED_OUTFIT_ID]}) + end + + set_enabled('replace_btn', false) + set_enabled('add_btn', false) +end + +function flt:post_build(event_data) + populate_list() +end + +function flt:commit_replace_btn(event_data) + if SHOW_OUTFITS then + LLAppearance.wearOutfit(get_selected_id(), 'replace') + else + LLAppearance.wearItems(get_selected_id(), false) + end +end + +function flt:commit_add_btn(event_data) + if SHOW_OUTFITS then + LLAppearance.wearOutfit(get_selected_id(), 'add') + else + LLAppearance.detachItems(get_selected_id()) + end +end + +function flt:commit_select_btn(event_data) + SHOW_OUTFITS = not SHOW_OUTFITS + SELECTED_OUTFIT_ID = get_selected_id() + update_labels() + self:post({action="clear_list", ctrl_name='outfits_list'}) + populate_list() +end + +function flt:commit_outfits_list(event_data) + set_enabled('replace_btn', true) + set_enabled('add_btn', true) + set_enabled('select_btn', true) +end + +startup.wait('STATE_STARTED') +flt:show() diff --git a/indra/newview/scripts/lua/test_outfits_list.lua b/indra/newview/scripts/lua/test_outfits_list.lua deleted file mode 100644 index 1011029f34..0000000000 --- a/indra/newview/scripts/lua/test_outfits_list.lua +++ /dev/null @@ -1,114 +0,0 @@ -local Floater = require 'Floater' -local LLAppearance = require 'LLAppearance' -local startup = require 'startup' -local inspect = require 'inspect' - -local SHOW_OUTFITS = true -local SELECTED_OUTFIT_ID = {} -local DATA_MAP = {} - -local wearables_lbl = 'Show wearables' -local outfits_lbl = 'Show outfits' -local replace_cof_lbl = 'Replace COF' -local add_cof_lbl = 'Add to COF' -local outfits_title = 'Outfits' -local wear_lbl = 'Wear item' -local detach_lbl = 'Detach item' - -local flt = Floater:new( - "luafloater_outfits_list.xml", - {outfits_list = {"double_click"}}) - -function get_selected_id() - return flt:request({action="get_selected_id", ctrl_name='outfits_list'}).value -end - -function populate_list() - if SHOW_OUTFITS then - DATA_MAP = LLAppearance.getOutfitsList() - else - DATA_MAP = LLAppearance.getOutfitItems(SELECTED_OUTFIT_ID) - end - - local action_data = {} - action_data.action = "add_list_element" - action_data.ctrl_name = "outfits_list" - local outfits = {} - for uuid, info in pairs(DATA_MAP) do - name = {} - if SHOW_OUTFITS then - name = info - else - name = info.name - end - table.insert(outfits, {value = uuid, columns={column = "outfit_name", value = name}}) - end - action_data.value = outfits - flt:post(action_data) -end - -function set_label(btn_name, value) - flt:post({action="set_label", ctrl_name=btn_name, value=value}) -end - -function set_enabled(btn_name, value) - flt:post({action="set_enabled", ctrl_name=btn_name, value=value}) -end - -function update_labels() - if SHOW_OUTFITS then - set_label('select_btn', wearables_lbl) - set_label('replace_btn', replace_cof_lbl) - set_label('add_btn', add_cof_lbl) - - set_enabled('select_btn', false) - flt:post({action="set_title", value=outfits_title}) - else - set_label('select_btn', outfits_lbl) - set_label('replace_btn', wear_lbl) - set_label('add_btn', detach_lbl) - - set_enabled('select_btn', true) - flt:post({action="set_title", value=DATA_MAP[SELECTED_OUTFIT_ID]}) - end - - set_enabled('replace_btn', false) - set_enabled('add_btn', false) -end - -function flt:post_build(event_data) - populate_list() -end - -function flt:commit_replace_btn(event_data) - if SHOW_OUTFITS then - LLAppearance.replaceOutfit(get_selected_id()) - else - LLAppearance.wearItem(get_selected_id(), false) - end -end - -function flt:commit_add_btn(event_data) - if SHOW_OUTFITS then - LLAppearance.addOutfit(get_selected_id()) - else - LLAppearance.detachItem(get_selected_id()) - end -end - -function flt:commit_select_btn(event_data) - SHOW_OUTFITS = not SHOW_OUTFITS - SELECTED_OUTFIT_ID = get_selected_id() - update_labels() - self:post({action="clear_list", ctrl_name='outfits_list'}) - populate_list() -end - -function flt:commit_outfits_list(event_data) - set_enabled('replace_btn', true) - set_enabled('add_btn', true) - set_enabled('select_btn', true) -end - -startup.wait('STATE_STARTED') -flt:show() -- cgit v1.2.3 From b27606feca00172cdfd7467ff3e216824f1c9518 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Mon, 8 Jul 2024 13:56:46 +0300 Subject: Lua api for Snapshot and demo script --- indra/newview/llviewerwindow.cpp | 2 +- indra/newview/llviewerwindowlistener.cpp | 52 +++++++++++++++++++---------- indra/newview/scripts/lua/require/UI.lua | 15 +++++++++ indra/newview/scripts/lua/test_snapshot.lua | 15 +++++++++ 4 files changed, 65 insertions(+), 19 deletions(-) create mode 100644 indra/newview/scripts/lua/test_snapshot.lua (limited to 'indra') diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index b637bcbdac..9f60f71d28 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -4840,7 +4840,7 @@ BOOL LLViewerWindow::saveSnapshot(const std::string& filepath, S32 image_width, LL_INFOS() << "Saving snapshot to: " << filepath << LL_ENDL; LLPointer raw = new LLImageRaw; - BOOL success = rawSnapshot(raw, image_width, image_height, TRUE, FALSE, show_ui, show_hud, do_rebuild); + BOOL success = rawSnapshot(raw, image_width, image_height, TRUE, FALSE, show_ui, show_hud, do_rebuild, 0, type); if (success) { diff --git a/indra/newview/llviewerwindowlistener.cpp b/indra/newview/llviewerwindowlistener.cpp index da7e18af5c..cba4fbc391 100644 --- a/indra/newview/llviewerwindowlistener.cpp +++ b/indra/newview/llviewerwindowlistener.cpp @@ -43,22 +43,13 @@ LLViewerWindowListener::LLViewerWindowListener(LLViewerWindow* llviewerwindow): mViewerWindow(llviewerwindow) { // add() every method we want to be able to invoke via this event API. - LLSD saveSnapshotArgs; - saveSnapshotArgs["filename"] = LLSD::String(); - saveSnapshotArgs["reply"] = LLSD::String(); - // The following are optional, so don't build them into required prototype. -// saveSnapshotArgs["width"] = LLSD::Integer(); -// saveSnapshotArgs["height"] = LLSD::Integer(); -// saveSnapshotArgs["showui"] = LLSD::Boolean(); -// saveSnapshotArgs["showhud"] = LLSD::Boolean(); -// saveSnapshotArgs["rebuild"] = LLSD::Boolean(); -// saveSnapshotArgs["type"] = LLSD::String(); add("saveSnapshot", - "Save screenshot: [\"filename\"], [\"width\"], [\"height\"], [\"showui\"], [\"showhud\"], [\"rebuild\"], [\"type\"]\n" + "Save screenshot: [\"filename\"] (extension may be specified: bmp, jpeg, png)\n" + "[\"width\"], [\"height\"], [\"showui\"], [\"showhud\"], [\"rebuild\"], [\"type\"]\n" "type: \"COLOR\", \"DEPTH\"\n" "Post on [\"reply\"] an event containing [\"ok\"]", &LLViewerWindowListener::saveSnapshot, - saveSnapshotArgs); + llsd::map("filename", LLSD(), "reply", LLSD())); add("requestReshape", "Resize the window: [\"w\"], [\"h\"]", &LLViewerWindowListener::requestReshape); @@ -66,12 +57,15 @@ LLViewerWindowListener::LLViewerWindowListener(LLViewerWindow* llviewerwindow): void LLViewerWindowListener::saveSnapshot(const LLSD& event) const { + Response response(LLSD(), event); + typedef std::map TypeMap; TypeMap types; #define tp(name) types[#name] = LLSnapshotModel::SNAPSHOT_TYPE_##name tp(COLOR); tp(DEPTH); -#undef tp +#undef tp + // Our add() call should ensure that the incoming LLSD does in fact // contain our required arguments. Deal with the optional ones. S32 width (mViewerWindow->getWindowWidthRaw()); @@ -94,14 +88,36 @@ void LLViewerWindowListener::saveSnapshot(const LLSD& event) const TypeMap::const_iterator found = types.find(event["type"]); if (found == types.end()) { - LL_ERRS("LLViewerWindowListener") << "LLViewerWindowListener::saveSnapshot(): " - << "unrecognized type " << event["type"] << LL_ENDL; - return; + return response.error(stringize("Unrecognized type ", std::quoted(event["type"].asString()), " [\"COLOR\"] or [\"DEPTH\"] is expected.")); } type = found->second; } - bool ok = mViewerWindow->saveSnapshot(event["filename"], width, height, showui, showhud, rebuild, type); - sendReply(LLSDMap("ok", ok), event); + + std::string filename(event["filename"]); + if (filename.empty()) + { + return response.error(stringize("File path is empty.")); + } + + LLSnapshotModel::ESnapshotFormat format(LLSnapshotModel::SNAPSHOT_FORMAT_BMP); + std::string ext = gDirUtilp->getExtension(filename); + if (ext.empty()) + { + filename.append(".bmp"); + } + else if (ext == "png") + { + format = LLSnapshotModel::SNAPSHOT_FORMAT_PNG; + } + else if (ext == "jpeg" || ext == "jpg") + { + format = LLSnapshotModel::SNAPSHOT_FORMAT_JPEG; + } + else if (ext != "bmp") + { + return response.error(stringize("Unrecognized format. [\"png\"], [\"jpeg\"] or [\"bmp\"] is expected.")); + } + response["result"] = mViewerWindow->saveSnapshot(filename, width, height, showui, showhud, rebuild, type, format); } void LLViewerWindowListener::requestReshape(LLSD const & event_data) const diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua index eb1a4017c7..8fcc446e09 100644 --- a/indra/newview/scripts/lua/require/UI.lua +++ b/indra/newview/scripts/lua/require/UI.lua @@ -122,4 +122,19 @@ function UI.type(...) end end +-- *************************************************************************** +-- Snapshot +-- *************************************************************************** +-- UI.snapshot{filename=filename -- extension may be specified: bmp, jpeg, png +-- [, type='COLOR' | 'DEPTH'] +-- [, width=width][, height=height] -- uses current window size if not specified +-- [, showui=true][, showhud=true] +-- [, rebuild=false]} +function UI.snapshot(...) + local args = mapargs('filename,width,height,showui,showhud,rebuild,type', ...) + leap.request('LLViewerWindow', {op='saveSnapshot', filename = args.filename, + width=args.width, height=args.height, + showui=args.showui, showhud=args.showhud, + rebuild=args.rebuild, type=args.type}) +end return UI diff --git a/indra/newview/scripts/lua/test_snapshot.lua b/indra/newview/scripts/lua/test_snapshot.lua new file mode 100644 index 0000000000..d7c878833b --- /dev/null +++ b/indra/newview/scripts/lua/test_snapshot.lua @@ -0,0 +1,15 @@ +local UI = require 'UI' + +PATH = 'E:\\' +-- 'png', 'jpeg' or 'bmp' +EXT = '.png' + +NAME_SIMPLE = 'Snapshot_simple' .. '_' .. os.date("%Y-%m-%d_%H-%M-%S") +UI.snapshot(PATH .. NAME_SIMPLE .. EXT) + +NAME_ARGS = 'Snapshot_args' .. '_' .. os.date("%Y-%m-%d_%H-%M-%S") + +-- 'COLOR' or 'DEPTH' +TYPE = 'COLOR' +UI.snapshot{PATH .. NAME_ARGS .. EXT, width = 700, height = 400, + type = TYPE, showui = false, showhud = false} -- cgit v1.2.3 From 6f62e8b59191ee622591c91a55d02bb6c808e85a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 8 Jul 2024 16:21:51 -0400 Subject: Quote "LLAppearance" op="wearOutfit" folder_id and folder_name args --- indra/newview/llappearancelistener.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/llappearancelistener.cpp b/indra/newview/llappearancelistener.cpp index 75eaf29186..b8aca96b43 100644 --- a/indra/newview/llappearancelistener.cpp +++ b/indra/newview/llappearancelistener.cpp @@ -38,7 +38,7 @@ LLAppearanceListener::LLAppearanceListener() "API to wear a specified outfit and wear/remove individual items") { add("wearOutfit", - "Wear outfit by folder id: [folder_id] OR by folder name: [folder_name]\n" + "Wear outfit by folder id: [\"folder_id\"] OR by folder name: [\"folder_name\"]\n" "When [\"append\"] is true, outfit will be added to COF\n" "otherwise it will replace current oufit", &LLAppearanceListener::wearOutfit); -- cgit v1.2.3 From 2918ebc081f63bffd5c482375f7fcbbb2faea175 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 8 Jul 2024 16:23:29 -0400 Subject: Combine LLAppearanceMgr::wearOutfit() and wearOutfitByName() into new private wearOutfit(LLInventoryCategory*) method. --- indra/newview/llappearancemgr.cpp | 63 +++++++++++++++------------------------ indra/newview/llappearancemgr.h | 4 +++ 2 files changed, 28 insertions(+), 39 deletions(-) (limited to 'indra') diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 7a34006323..a8bc9d8e52 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -2904,6 +2904,17 @@ void LLAppearanceMgr::wearInventoryCategoryOnAvatar( LLInventoryCategory* catego LLAppearanceMgr::changeOutfit(TRUE, category->getUUID(), append); } +bool LLAppearanceMgr::wearOutfitByName(const std::string& name, bool append) +{ + std::string error_msg; + if(!wearOutfitByName(name, error_msg, append)) + { + LL_WARNS() << error_msg << LL_ENDL; + return false; + } + return true; +} + bool LLAppearanceMgr::wearOutfitByName(const std::string& name, std::string& error_msg, bool append) { LL_INFOS("Avatar") << self_av_string() << "Wearing category " << name << LL_ENDL; @@ -2937,63 +2948,37 @@ bool LLAppearanceMgr::wearOutfitByName(const std::string& name, std::string& err } } - if(cat) - { - // don't allow wearing a system folder - if (LLFolderType::lookupIsProtectedType(cat->getPreferredType())) - { - error_msg = stringize(LLTrans::getString("SystemFolderNotWorn"), std::quoted(name)); - return false; - } - bool can_wear = append ? getCanAddToCOF(cat->getUUID()) : getCanReplaceCOF(cat->getUUID()); - if (!can_wear) - { - std::string msg = append ? LLTrans::getString("OutfitNotAdded") : LLTrans::getString("OutfitNotReplaced"); - error_msg = stringize(msg, std::quoted(name), ", id: ", cat->getUUID()); - return false; - } - LLAppearanceMgr::wearInventoryCategory(cat, copy_items, append); - } - else - { - error_msg = stringize(LLTrans::getString("OutfitNotFound"), std::quoted(name)); - return false; - } - return true; + return wearOutfit(std::quoted(name), cat, error_msg, copy_items, append); } bool LLAppearanceMgr::wearOutfit(const LLUUID &cat_id, std::string &error_msg, bool append) { LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); + return wearOutfit(stringize(cat_id), cat, error_msg, false, append); +} + +bool LLAppearanceMgr::wearOutfit(const std::string &desc, LLInventoryCategory* cat, + std::string &error_msg, bool copy_items, bool append) +{ if (!cat) { - error_msg = stringize(LLTrans::getString("OutfitNotFound"), cat_id); + error_msg = stringize(LLTrans::getString("OutfitNotFound"), desc); return false; } + // don't allow wearing a system folder if (LLFolderType::lookupIsProtectedType(cat->getPreferredType())) { - error_msg = stringize(LLTrans::getString("SystemFolderNotWorn"), cat_id); + error_msg = stringize(LLTrans::getString("SystemFolderNotWorn"), std::quoted(cat->getName())); return false; } - bool can_wear = append ? LLAppearanceMgr::instance().getCanAddToCOF(cat_id) : LLAppearanceMgr::instance().getCanReplaceCOF(cat_id); + bool can_wear = append ? getCanAddToCOF(cat->getUUID()) : getCanReplaceCOF(cat->getUUID()); if (!can_wear) { std::string msg = append ? LLTrans::getString("OutfitNotAdded") : LLTrans::getString("OutfitNotReplaced"); - error_msg = stringize(msg, std::quoted(cat->getName()), " , id: ", cat_id); - return false; - } - LLAppearanceMgr::instance().wearInventoryCategory(cat, false, append); - return true; -} - -bool LLAppearanceMgr::wearOutfitByName(const std::string& name, bool append) -{ - std::string error_msg; - if(!wearOutfitByName(name, error_msg, append)) - { - LL_WARNS() << error_msg << LL_ENDL; + error_msg = stringize(msg, std::quoted(cat->getName()), " , id: ", cat->getUUID()); return false; } + wearInventoryCategory(cat, copy_items, append); return true; } diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index b795494f94..11d209e6b5 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -263,6 +263,10 @@ private: static void onOutfitRename(const LLSD& notification, const LLSD& response); + // used by both wearOutfit(LLUUID) and wearOutfitByName(std::string) + bool wearOutfit(const std::string &desc, LLInventoryCategory* cat, + std::string &error_msg, bool copy_items, bool append); + bool mAttachmentInvLinkEnabled; bool mOutfitIsDirty; bool mIsInUpdateAppearanceFromCOF; // to detect recursive calls. -- cgit v1.2.3 From 0e5cee5d6ddad76ccd37b5ed6bf8d692435bb7dc Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 8 Jul 2024 16:40:02 -0400 Subject: Slightly simplify LLAppearanceListener::wearItems(), detachItems(). --- indra/newview/llappearancelistener.cpp | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) (limited to 'indra') diff --git a/indra/newview/llappearancelistener.cpp b/indra/newview/llappearancelistener.cpp index b8aca96b43..c39799c5e9 100644 --- a/indra/newview/llappearancelistener.cpp +++ b/indra/newview/llappearancelistener.cpp @@ -91,36 +91,38 @@ void LLAppearanceListener::wearOutfit(LLSD const &data) void LLAppearanceListener::wearItems(LLSD const &data) { - if (data["items_id"].isArray()) + const LLSD& items_id{ data["items_id"] }; + uuid_vec_t ids; + if (! items_id.isArray()) { - uuid_vec_t ids; - for (const auto &id : llsd::inArray(data["items_id"])) + ids.push_back(items_id.asUUID()); + } + else // array + { + for (const auto &id : llsd::inArray(items_id)) { ids.push_back(id); } - LLAppearanceMgr::instance().wearItemsOnAvatar(ids, true, data["replace"].asBoolean()); - } - else - { - LLAppearanceMgr::instance().wearItemOnAvatar(data["items_id"].asUUID(), true, data["replace"].asBoolean()); } + LLAppearanceMgr::instance().wearItemsOnAvatar(ids, true, data["replace"].asBoolean()); } void LLAppearanceListener::detachItems(LLSD const &data) { - if (data["items_id"].isArray()) + const LLSD& items_id{ data["items_id"] }; + uuid_vec_t ids; + if (! items_id.isArray()) { - uuid_vec_t ids; - for (const auto &id : llsd::inArray(data["items_id"])) + ids.push_back(items_id.asUUID()); + } + else // array + { + for (const auto &id : llsd::inArray(items_id)) { ids.push_back(id); } - LLAppearanceMgr::instance().removeItemsFromAvatar(ids); - } - else - { - LLAppearanceMgr::instance().removeItemFromAvatar(data["items_id"].asUUID()); } + LLAppearanceMgr::instance().removeItemsFromAvatar(ids); } void LLAppearanceListener::getOutfitsList(LLSD const &data) -- cgit v1.2.3 From b8c09ed80d076f44b08d6aaa59fffd98abd55811 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 8 Jul 2024 17:18:48 -0400 Subject: The I/O manipulator std::quoted() must be passed to an ostream. --- indra/newview/llappearancemgr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index a8bc9d8e52..26c41b19ed 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -2948,7 +2948,7 @@ bool LLAppearanceMgr::wearOutfitByName(const std::string& name, std::string& err } } - return wearOutfit(std::quoted(name), cat, error_msg, copy_items, append); + return wearOutfit(stringize(std::quoted(name)), cat, error_msg, copy_items, append); } bool LLAppearanceMgr::wearOutfit(const LLUUID &cat_id, std::string &error_msg, bool append) -- cgit v1.2.3 From f009f8da6b140f02f6463a43d336ab2644782fa1 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 9 Jul 2024 10:24:32 -0400 Subject: Introduce LLSDParam> and LLSDParam>. Use LLSDParam in LLAppearanceListener::wearItems() and detachItems() to build the vector of LLUUIDs from the passed LLSD array. --- indra/llcommon/llsdutil.h | 61 ++++++++++++++++++++++++++++++++++ indra/newview/llappearancelistener.cpp | 33 +++--------------- 2 files changed, 66 insertions(+), 28 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index 7c31dc8aa0..3f59222e9b 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -524,6 +524,67 @@ private: } // namespace llsd +/***************************************************************************** +* LLSDParam> +*****************************************************************************/ +// Given an LLSD array, return a const std::vector&, where T is a type +// supported by LLSDParam. Bonus: if the LLSD value is actually a scalar, +// return a single-element vector containing the converted value. +template +class LLSDParam>: public LLSDParamBase +{ +public: + LLSDParam(const LLSD& array) + { + // treat undefined "array" as empty vector + if (array.isDefined()) + { + // what if it's a scalar? + if (! array.isArray()) + { + v.push_back(LLSDParam(array)); + } + else // really is an array + { + // reserve space for the array entries + v.reserve(array.size()); + for (const auto& item : llsd::inArray(array)) + { + v.push_back(LLSDParam(item)); + } + } + } + } + + operator const std::vector&() const { return v; } + +private: + std::vector v; +}; + +/***************************************************************************** +* LLSDParam> +*****************************************************************************/ +// Given an LLSD map, return a const std::map&, where T is a +// type supported by LLSDParam. +template +class LLSDParam>: public LLSDParamBase +{ +public: + LLSDParam(const LLSD& map) + { + for (const auto& pair : llsd::inMap(map)) + { + m[pair.first] = LLSDParam(pair.second); + } + } + + operator const std::map&() const { return m; } + +private: + std::map m; +}; + /***************************************************************************** * deep and shallow clone *****************************************************************************/ diff --git a/indra/newview/llappearancelistener.cpp b/indra/newview/llappearancelistener.cpp index c39799c5e9..0dab352311 100644 --- a/indra/newview/llappearancelistener.cpp +++ b/indra/newview/llappearancelistener.cpp @@ -91,38 +91,15 @@ void LLAppearanceListener::wearOutfit(LLSD const &data) void LLAppearanceListener::wearItems(LLSD const &data) { - const LLSD& items_id{ data["items_id"] }; - uuid_vec_t ids; - if (! items_id.isArray()) - { - ids.push_back(items_id.asUUID()); - } - else // array - { - for (const auto &id : llsd::inArray(items_id)) - { - ids.push_back(id); - } - } - LLAppearanceMgr::instance().wearItemsOnAvatar(ids, true, data["replace"].asBoolean()); + LLAppearanceMgr::instance().wearItemsOnAvatar( + LLSDParam(data["items_id"]), + true, data["replace"].asBoolean()); } void LLAppearanceListener::detachItems(LLSD const &data) { - const LLSD& items_id{ data["items_id"] }; - uuid_vec_t ids; - if (! items_id.isArray()) - { - ids.push_back(items_id.asUUID()); - } - else // array - { - for (const auto &id : llsd::inArray(items_id)) - { - ids.push_back(id); - } - } - LLAppearanceMgr::instance().removeItemsFromAvatar(ids); + LLAppearanceMgr::instance().removeItemsFromAvatar( + LLSDParam(data["items_id"])); } void LLAppearanceListener::getOutfitsList(LLSD const &data) -- cgit v1.2.3 From 98761798b9a118c46e5482b1f36fef1260c9c5f9 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Wed, 10 Jul 2024 12:03:49 +0300 Subject: Simplify passing keys to leap.request --- indra/newview/llviewerwindowlistener.cpp | 4 ++-- indra/newview/scripts/lua/require/UI.lua | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'indra') diff --git a/indra/newview/llviewerwindowlistener.cpp b/indra/newview/llviewerwindowlistener.cpp index cba4fbc391..52f413792a 100644 --- a/indra/newview/llviewerwindowlistener.cpp +++ b/indra/newview/llviewerwindowlistener.cpp @@ -47,9 +47,9 @@ LLViewerWindowListener::LLViewerWindowListener(LLViewerWindow* llviewerwindow): "Save screenshot: [\"filename\"] (extension may be specified: bmp, jpeg, png)\n" "[\"width\"], [\"height\"], [\"showui\"], [\"showhud\"], [\"rebuild\"], [\"type\"]\n" "type: \"COLOR\", \"DEPTH\"\n" - "Post on [\"reply\"] an event containing [\"ok\"]", + "Post on [\"reply\"] an event containing [\"result\"]", &LLViewerWindowListener::saveSnapshot, - llsd::map("filename", LLSD(), "reply", LLSD())); + llsd::map("filename", LLSD::String(), "reply", LLSD())); add("requestReshape", "Resize the window: [\"w\"], [\"h\"]", &LLViewerWindowListener::requestReshape); diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua index 8fcc446e09..1eee4657f4 100644 --- a/indra/newview/scripts/lua/require/UI.lua +++ b/indra/newview/scripts/lua/require/UI.lua @@ -132,9 +132,7 @@ end -- [, rebuild=false]} function UI.snapshot(...) local args = mapargs('filename,width,height,showui,showhud,rebuild,type', ...) - leap.request('LLViewerWindow', {op='saveSnapshot', filename = args.filename, - width=args.width, height=args.height, - showui=args.showui, showhud=args.showhud, - rebuild=args.rebuild, type=args.type}) + args.op = 'saveSnapshot' + return leap.request('LLViewerWindow', args).result end return UI -- cgit v1.2.3 From 8c94ff566a4f9076607d1b988f3eb7ad7e200bd9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 10 Jul 2024 15:14:13 -0400 Subject: Remove ability to reuse a LuaState between LLLUAmanager functions. Remove LLLUAmanager::mumbleScriptLine() LuaState& parameters. Make startScriptLine(), waitScriptLine() and runScriptLine() exactly parallel to startScriptFile(), waitScriptFile() and runScriptFile(). That means that runScriptLine()'s C++ coroutine instantiates and destroys its own LuaState, which means that LL.atexit() functions will run on the Lua-specific C++ coroutine rather than (say) the viewer's main coroutine. Introduce LLLUAmanager::script_result typedef for std::pair and use in method returns. Remove LuaState::initLuaState(); move its logic back into the constructor. Remove initLuaState() calls in the expr() error cases: they're moot now that we won't get subsequent expr() calls on the same LuaState instance. Remove LLFloaterLUADebug "Use clean lua_State" checkbox and the cleanLuaState() method. Remove mState member. Remove explicit LuaState declarations from LLLUAmanager tests. Adapt one test for implicit LuaState: it was directly calling LuaState::obtainListener() to discover the LuaListener's reply-pump name. But since that test also captures two leap.request() calls from the Lua script, it can just look at the "reply" key in either of those requests. --- indra/llcommon/lua_function.cpp | 18 ++------ indra/llcommon/lua_function.h | 2 - indra/newview/llfloaterluadebug.cpp | 13 +----- indra/newview/llfloaterluadebug.h | 2 - indra/newview/llluamanager.cpp | 48 +++++++++------------- indra/newview/llluamanager.h | 20 ++++----- .../skins/default/xui/en/floater_lua_debug.xml | 9 ---- indra/newview/tests/llluamanager_test.cpp | 34 ++++++--------- 8 files changed, 47 insertions(+), 99 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 2d08de68c5..4acb09d564 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -478,19 +478,11 @@ void lua_pushllsd(lua_State* L, const LLSD& data) *****************************************************************************/ LuaState::LuaState(script_finished_fn cb): mCallback(cb), - mState(nullptr) + mState(luaL_newstate()) { - initLuaState(); -} - -void LuaState::initLuaState() -{ - if (mState) - { - lua_close(mState); - } - mState = luaL_newstate(); luaL_openlibs(mState); + // publish to this new lua_State all the LL entry points we defined using + // the lua_function() macro LuaFunction::init(mState); // Try to make print() write to our log. lua_register(mState, "print", LuaFunction::get("print_info")); @@ -607,8 +599,7 @@ std::pair LuaState::expr(const std::string& desc, const std::string& // 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(); + // chunk runs successfully. return { -1, stringize(LLError::Log::classname(error), ": ", error.what()) }; } } @@ -628,7 +619,6 @@ std::pair LuaState::expr(const std::string& desc, const std::string& LL_WARNS("Lua") << desc << " error converting result " << index << ": " << error.what() << LL_ENDL; // see above comments regarding lua_State's error status - initLuaState(); return { -1, stringize(LLError::Log::classname(error), ": ", error.what()) }; } } diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index b3b1f40ae5..7f7a7566f3 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -87,8 +87,6 @@ public: ~LuaState(); - void initLuaState(); - bool checkLua(const std::string& desc, int r); // expr() is for when we want to capture any results left on the stack diff --git a/indra/newview/llfloaterluadebug.cpp b/indra/newview/llfloaterluadebug.cpp index f715327ec8..9981126e4f 100644 --- a/indra/newview/llfloaterluadebug.cpp +++ b/indra/newview/llfloaterluadebug.cpp @@ -27,7 +27,6 @@ #include "llfloaterluadebug.h" -#include "llcheckboxctrl.h" #include "lllineeditor.h" #include "lltexteditor.h" #include "llviewermenufile.h" // LLFilePickerReplyThread @@ -92,8 +91,7 @@ void LLFloaterLUADebug::onExecuteClicked() mResultOutput->setValue(""); std::string cmd = mLineInput->getText(); - cleanLuaState(); - LLLUAmanager::runScriptLine(mState, cmd, [this](int count, const LLSD& result) + LLLUAmanager::runScriptLine(cmd, [this](int count, const LLSD& result) { completion(count, result); }); @@ -174,12 +172,3 @@ void LLFloaterLUADebug::completion(int count, const LLSD& result) sep = ", "; } } - -void LLFloaterLUADebug::cleanLuaState() -{ - if(getChild("clean_lua_state")->get()) - { - //Reinit to clean lua_State - mState.initLuaState(); - } -} diff --git a/indra/newview/llfloaterluadebug.h b/indra/newview/llfloaterluadebug.h index ae30b7cf25..e513d9a095 100644 --- a/indra/newview/llfloaterluadebug.h +++ b/indra/newview/llfloaterluadebug.h @@ -58,14 +58,12 @@ class LLFloaterLUADebug : private: void completion(int count, const LLSD& result); - void cleanLuaState(); LLTempBoundListener mOutConnection; LLTextEditor* mResultOutput; LLLineEditor* mLineInput; LLLineEditor* mScriptPath; - LuaState mState; U32 mAck{ 0 }; bool mExecuting{ false }; }; diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index ccfa08078e..7014c59e4e 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -162,24 +162,25 @@ lua_function(get_event_next, return 2; } -LLCoros::Future> +LLCoros::Future LLLUAmanager::startScriptFile(const std::string& filename) { // Despite returning from startScriptFile(), we need this Promise to // remain alive until the callback has fired. - auto promise{ std::make_shared>>() }; + auto promise{ std::make_shared>() }; runScriptFile(filename, [promise](int count, LLSD result) { promise->set_value({ count, result }); }); return LLCoros::getFuture(*promise); } -std::pair LLLUAmanager::waitScriptFile(const std::string& filename) +LLLUAmanager::script_result LLLUAmanager::waitScriptFile(const std::string& filename) { return startScriptFile(filename).get(); } -void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn result_cb, script_finished_fn finished_cb) +void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn result_cb, + script_finished_fn finished_cb) { // A script_result_fn will be called when LuaState::expr() completes. LLCoros::instance().launch(filename, [filename, result_cb, finished_cb]() @@ -212,39 +213,25 @@ void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn r }); } -void LLLUAmanager::runScriptLine(const std::string& chunk, 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, chunk); -} - -void LLLUAmanager::runScriptLine(const std::string& chunk, script_result_fn cb) -{ - LuaState L; - // A script_result_fn will be called when LuaState::expr() completes. - runScriptLine(L, chunk, cb); -} - -LLCoros::Future> -LLLUAmanager::startScriptLine(LuaState& L, const std::string& chunk) +LLCoros::Future +LLLUAmanager::startScriptLine(const std::string& chunk) { // Despite returning from startScriptLine(), we need this Promise to // remain alive until the callback has fired. - auto promise{ std::make_shared>>() }; - runScriptLine(L, chunk, + auto promise{ std::make_shared>() }; + runScriptLine(chunk, [promise](int count, LLSD result) { promise->set_value({ count, result }); }); return LLCoros::getFuture(*promise); } -std::pair LLLUAmanager::waitScriptLine(LuaState& L, const std::string& chunk) +LLLUAmanager::script_result LLLUAmanager::waitScriptLine(const std::string& chunk) { - return startScriptLine(L, chunk).get(); + return startScriptLine(chunk).get(); } -void LLLUAmanager::runScriptLine(LuaState& L, const std::string& chunk, script_result_fn cb) +void LLLUAmanager::runScriptLine(const std::string& chunk, script_result_fn result_cb, + script_finished_fn finished_cb) { // find a suitable abbreviation for the chunk string std::string shortchunk{ chunk }; @@ -256,12 +243,15 @@ void LLLUAmanager::runScriptLine(LuaState& L, const std::string& chunk, script_r shortchunk = stringize(shortchunk.substr(0, shortlen), "..."); std::string desc{ "lua: " + shortchunk }; - LLCoros::instance().launch(desc, [&L, desc, chunk, cb]() + LLCoros::instance().launch(desc, [desc, chunk, result_cb, finished_cb]() { + // A script_finished_fn is used to initialize the LuaState. + // It will be called when the LuaState is destroyed. + LuaState L(finished_cb); auto [count, result] = L.expr(desc, chunk); - if (cb) + if (result_cb) { - cb(count, result); + result_cb(count, result); } }); } diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h index dcbb91f799..50f922a80f 100644 --- a/indra/newview/llluamanager.h +++ b/indra/newview/llluamanager.h @@ -55,32 +55,32 @@ public: // count > 1 with result.isArray() means the script returned multiple // results, represented as the entries of the result array. typedef std::function script_result_fn; + // same semantics as script_result_fn parameters + typedef std::pair script_result; - static void runScriptFile(const std::string &filename, script_result_fn result_cb = {}, script_finished_fn finished_cb = {}); + static void runScriptFile(const std::string &filename, script_result_fn result_cb = {}, + script_finished_fn finished_cb = {}); // Start running a Lua script file, returning an LLCoros::Future whose // get() method will pause the calling coroutine until it can deliver the // (count, result) pair described above. Between startScriptFile() and // Future::get(), the caller and the Lua script coroutine will run // concurrently. - static LLCoros::Future> - startScriptFile(const std::string& filename); + static LLCoros::Future startScriptFile(const std::string& filename); // 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 waitScriptFile(const std::string& filename); + static script_result waitScriptFile(const std::string& filename); - static void runScriptLine(const std::string &chunk, script_finished_fn cb = {}); - static void runScriptLine(const std::string &chunk, script_result_fn cb); - static void runScriptLine(LuaState& L, const std::string &chunk, script_result_fn cb = {}); + static void runScriptLine(const std::string &chunk, script_result_fn result_cb = {}, + script_finished_fn finished_cb = {}); // Start running a Lua chunk, returning an LLCoros::Future whose // get() method will pause the calling coroutine until it can deliver the // (count, result) pair described above. Between startScriptLine() and // Future::get(), the caller and the Lua script coroutine will run // concurrently. - static LLCoros::Future> - startScriptLine(LuaState& L, const std::string& chunk); + static LLCoros::Future startScriptLine(const std::string& chunk); // Run a Lua chunk, and pause the calling coroutine until it completes. // The return value is the (count, result) pair described above. - static std::pair waitScriptLine(LuaState& L, const std::string& chunk); + static script_result waitScriptLine(const std::string& chunk); static const std::map getScriptNames() { return sScriptNames; } diff --git a/indra/newview/skins/default/xui/en/floater_lua_debug.xml b/indra/newview/skins/default/xui/en/floater_lua_debug.xml index 012ea6f254..15027f1647 100644 --- a/indra/newview/skins/default/xui/en/floater_lua_debug.xml +++ b/indra/newview/skins/default/xui/en/floater_lua_debug.xml @@ -25,15 +25,6 @@ width="100"> LUA string: - () { set_test_name("test Lua results"); - LuaState L; for (auto& luax : lua_expressions) { auto [count, result] = - LLLUAmanager::waitScriptLine(L, "return " + luax.expr); + LLLUAmanager::waitScriptLine("return " + luax.expr); auto desc{ stringize("waitScriptLine(", luax.desc, "): ") }; // if count < 0, report Lua error message ensure_equals(desc + result.asString(), count, 1); @@ -144,8 +143,7 @@ namespace tut "data = ", construct, "\n" "LL.post_on('testpump', data)\n" )); - LuaState L; - auto [count, result] = LLLUAmanager::waitScriptLine(L, lua); + auto [count, result] = LLLUAmanager::waitScriptLine(lua); // We woke up again ourselves because the coroutine running Lua has // finished. But our Lua chunk didn't actually return anything, so we // expect count to be 0 and result to be undefined. @@ -182,11 +180,10 @@ namespace tut "LL.post_on('testpump', data)\n" "LL.post_on('testpump', 'exit')\n" ); - LuaState L; // It's important to let the startScriptLine() coroutine run // concurrently with ours until we've had a chance to post() our // reply. - auto future = LLLUAmanager::startScriptLine(L, lua); + auto future = LLLUAmanager::startScriptLine(lua); StringVec expected{ "entry", "get_event_pumps()", @@ -217,8 +214,7 @@ namespace tut "pump, data = LL.get_event_next()\n" "return data\n" ); - LuaState L; - auto future = LLLUAmanager::startScriptLine(L, lua); + auto future = LLLUAmanager::startScriptLine(lua); // We woke up again ourselves because the coroutine running Lua has // reached the get_event_next() call, which suspends the calling C++ // coroutine (including the Lua code running on it) until we post @@ -356,8 +352,7 @@ namespace tut sendReply(data, data); })); - LuaState L; - auto [count, result] = LLLUAmanager::waitScriptLine(L, lua); + auto [count, result] = LLLUAmanager::waitScriptLine(lua); ensure_equals("Lua script didn't return item", count, 1); ensure_equals("echo failed", result, llsd::map("a", "a", "b", "b")); } @@ -371,18 +366,18 @@ namespace tut "\n" "fiber = require('fiber')\n" "leap = require('leap')\n" - "-- debug = require('printf')\n" "local function debug(...) end\n" + "-- debug = require('printf')\n" "\n" "-- negative priority ensures catchall is always last\n" - "catchall = leap.WaitFor:new(-1, 'catchall')\n" + "catchall = leap.WaitFor(-1, 'catchall')\n" "function catchall:filter(pump, data)\n" " debug('catchall:filter(%s, %s)', pump, data)\n" " return data\n" "end\n" "\n" "-- but first, catch events with 'special' key\n" - "catch_special = leap.WaitFor:new(2, 'catch_special')\n" + "catch_special = leap.WaitFor(2, 'catch_special')\n" "function catch_special:filter(pump, data)\n" " debug('catch_special:filter(%s, %s)', pump, data)\n" " return if data['special'] ~= nil then data else nil\n" @@ -429,10 +424,7 @@ namespace tut requests.append(data); })); - LuaState L; - auto future = LLLUAmanager::startScriptLine(L, lua); - auto replyname{ L.obtainListener().getReplyName() }; - auto& replypump{ LLEventPumps::instance().obtain(replyname) }; + auto future = LLLUAmanager::startScriptLine(lua); // LuaState::expr() periodically interrupts a running chunk to ensure // the rest of our coroutines get cycles. Nonetheless, for this test // we have to wait until both requester() coroutines have posted and @@ -444,6 +436,8 @@ namespace tut llcoro::suspend(); } ensure_equals("didn't get both requests", requests.size(), 2); + auto replyname{ requests[0]["reply"].asString() }; + auto& replypump{ LLEventPumps::instance().obtain(replyname) }; // moreover, we expect they arrived in the order they were created ensure_equals("a wasn't first", requests[0]["name"].asString(), "a"); ensure_equals("b wasn't second", requests[1]["name"].asString(), "b"); @@ -468,8 +462,7 @@ namespace tut "\n" "LL.get_event_next()\n" ); - LuaState L; - auto future = LLLUAmanager::startScriptLine(L, lua); + auto future = LLLUAmanager::startScriptLine(lua); // Poke LLTestApp to send its preliminary shutdown message. mApp.setQuitting(); // but now we have to give the startScriptLine() coroutine a chance to run @@ -491,8 +484,7 @@ namespace tut " x = 1\n" "end\n" ); - LuaState L; - auto [count, result] = LLLUAmanager::waitScriptLine(L, lua); + auto [count, result] = LLLUAmanager::waitScriptLine(lua); // We expect the above erroneous script has been forcibly terminated // because it ran too long without doing any actual work. ensure_equals(desc + " count: " + result.asString(), count, -1); -- cgit v1.2.3 From e84e65e88f9953fbc7f88408c9c2821f97f4dee6 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 12 Jul 2024 15:23:57 +0300 Subject: Show description and actual value of LLSD type setting --- indra/newview/llfloatersettingsdebug.cpp | 14 ++++++++++++++ indra/newview/llfloatersettingsdebug.h | 1 + .../skins/default/xui/en/floater_settings_debug.xml | 13 ++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/llfloatersettingsdebug.cpp b/indra/newview/llfloatersettingsdebug.cpp index 1c10db4e0d..a494b715a2 100644 --- a/indra/newview/llfloatersettingsdebug.cpp +++ b/indra/newview/llfloatersettingsdebug.cpp @@ -35,6 +35,7 @@ #include "llviewercontrol.h" #include "lltexteditor.h" #include "llclipboard.h" +#include "llsdutil.h" LLFloaterSettingsDebug::LLFloaterSettingsDebug(const LLSD& key) @@ -54,6 +55,7 @@ BOOL LLFloaterSettingsDebug::postBuild() mComment = getChild("comment_text"); mSettingName = getChild("setting_name_txt"); + mLLSDVal = getChild("llsd_text"); mCopyBtn = getChild("copy_btn"); getChild("filter_input")->setCommitCallback(boost::bind(&LLFloaterSettingsDebug::setSearchFilter, this, _2)); @@ -472,6 +474,17 @@ void LLFloaterSettingsDebug::updateControl(LLControlVariable* controlp) color_swatch->setValue(sd); break; } + case TYPE_LLSD: + { + mLLSDVal->setVisible(true); + std::string new_text = ll_pretty_print_sd(sd); + // Don't setText if not nessesary, it will reset scroll + if (mLLSDVal->getText() != new_text) + { + mLLSDVal->setText(new_text); + } + break; + } default: mComment->setText(std::string("unknown")); break; @@ -638,6 +651,7 @@ void LLFloaterSettingsDebug::hideUIControls() getChildView("val_text")->setVisible(false); getChildView("default_btn")->setVisible(false); getChildView("boolean_combo")->setVisible(false); + mLLSDVal->setVisible(false); mSettingName->setVisible(false); mCopyBtn->setVisible(false); mComment->setVisible(false); diff --git a/indra/newview/llfloatersettingsdebug.h b/indra/newview/llfloatersettingsdebug.h index e52d5ac863..20aa9bb159 100644 --- a/indra/newview/llfloatersettingsdebug.h +++ b/indra/newview/llfloatersettingsdebug.h @@ -69,6 +69,7 @@ private: protected: class LLTextEditor* mComment; + LLTextEditor* mLLSDVal; LLTextBox* mSettingName; LLButton* mCopyBtn; diff --git a/indra/newview/skins/default/xui/en/floater_settings_debug.xml b/indra/newview/skins/default/xui/en/floater_settings_debug.xml index 6fd8f2b255..0b8190df7e 100644 --- a/indra/newview/skins/default/xui/en/floater_settings_debug.xml +++ b/indra/newview/skins/default/xui/en/floater_settings_debug.xml @@ -69,7 +69,7 @@ visible="false" use_ellipses="true" text_color="White" - width="240"> + width="225"> Debug setting name + -- cgit v1.2.3 From c9a8de49df04c55b94eee0fe5fb1eb628701cdcb Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 12 Jul 2024 15:38:19 +0300 Subject: fix for 'Run' button --- indra/newview/llfloaterluadebug.cpp | 6 ------ 1 file changed, 6 deletions(-) (limited to 'indra') diff --git a/indra/newview/llfloaterluadebug.cpp b/indra/newview/llfloaterluadebug.cpp index 9981126e4f..ef24481464 100644 --- a/indra/newview/llfloaterluadebug.cpp +++ b/indra/newview/llfloaterluadebug.cpp @@ -104,12 +104,6 @@ void LLFloaterLUADebug::onBtnBrowse() void LLFloaterLUADebug::onBtnRun() { - if (mExecuting) - { - LL_DEBUGS("Lua") << "recursive call to onBtnRun()" << LL_ENDL; - return; - } - TempSet executing(mExecuting, true); std::vector filenames; std::string filepath = mScriptPath->getText(); if (!filepath.empty()) -- cgit v1.2.3 From 50d60c2518710e92cff05b806624b11ac714369f Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Wed, 17 Jul 2024 16:46:00 +0300 Subject: Lua api for adding new menu items to the Top menu --- indra/newview/lluilistener.cpp | 104 ++++++++++++++++++++++++++++ indra/newview/lluilistener.h | 9 ++- indra/newview/scripts/lua/require/UI.lua | 28 ++++++++ indra/newview/scripts/lua/test_top_menu.lua | 34 +++++++++ 4 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 indra/newview/scripts/lua/test_top_menu.lua (limited to 'indra') diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp index 4afd7f1766..c73c93859a 100644 --- a/indra/newview/lluilistener.cpp +++ b/indra/newview/lluilistener.cpp @@ -34,10 +34,13 @@ // std headers // external library headers // other Linden headers +#include "llmenugl.h" #include "llui.h" // getRootView(), resolvePath() #include "lluictrl.h" #include "llerror.h" +extern LLMenuBarGL* gMenuBarView; + #define THROTTLE_PERIOD 1.5 // required seconds between throttled functions #define MIN_THROTTLE 0.5 @@ -57,6 +60,31 @@ LLUIListener::LLUIListener(): "current value as [\"value\"] reply.", &LLUIListener::getValue, llsd::map("path", LLSD(), "reply", LLSD())); + + LLSD required_args = llsd::map("name", LLSD(), "label", LLSD(), "reply", LLSD()); + add("addMenu", + "Add new drop-down menu [\"name\"] with displayed [\"label\"] to the Top menu.", + &LLUIListener::addMenu, + required_args); + + required_args.insert("parent_menu", LLSD()); + add("addMenuBranch", + "Add new menu branch [\"name\"] with displayed [\"label\"]\n" + "to the [\"parent_menu\"] within the Top menu.", + &LLUIListener::addMenuBranch, + required_args); + + add("addMenuItem", + "Add new menu item [\"name\"] with displayed [\"label\"]\n" + "and call-on-click UI function [\"func\"] with optional [\"param\"]\n" + "to the [\"parent_menu\"] within the Top menu.", + &LLUIListener::addMenuItem, + required_args.with("func", LLSD())); + + add("addMenuSeparator", + "Add menu separator to the [\"parent_menu\"] within the Top menu.", + &LLUIListener::addMenuSeparator, + llsd::map("parent_menu", LLSD(), "reply", LLSD())); } typedef LLUICtrl::CommitCallbackInfo cb_info; @@ -120,3 +148,79 @@ void LLUIListener::getValue(const LLSD&event) const response.error(stringize("UI control ", std::quoted(event["path"].asString()), " was not found")); } } + +LLMenuGL::Params get_params(const LLSD&event) +{ + LLMenuGL::Params item_params; + item_params.name = event["name"]; + item_params.label = event["label"]; + item_params.can_tear_off = true; + return item_params; +} + +LLMenuGL* get_parent_menu(LLEventAPI::Response& response, const LLSD&event) +{ + LLMenuGL* parent_menu = gMenuBarView->findChildMenuByName(event["parent_menu"], true); + if(!parent_menu) + { + response.error(stringize("Parent menu ", std::quoted(event["parent_menu"].asString()), " was not found")); + } + return parent_menu; +} + +void LLUIListener::addMenu(const LLSD&event) const +{ + Response response(LLSD(), event); + LLMenuGL::Params item_params = get_params(event); + if(!gMenuBarView->appendMenu(LLUICtrlFactory::create(item_params))) + { + response.error(stringize("Menu ", std::quoted(event["name"].asString()), " was not added")); + } +} + +void LLUIListener::addMenuBranch(const LLSD&event) const +{ + Response response(LLSD(), event); + if(LLMenuGL* parent_menu = get_parent_menu(response, event)) + { + LLMenuGL::Params item_params = get_params(event); + if(!parent_menu->appendMenu(LLUICtrlFactory::create(item_params))) + { + response.error(stringize("Menu branch ", std::quoted(event["name"].asString()), " was not added")); + } + } +} + +void LLUIListener::addMenuItem(const LLSD&event) const +{ + Response response(LLSD(), event); + LLMenuItemCallGL::Params item_params; + item_params.name = event["name"]; + item_params.label = event["label"]; + LLUICtrl::CommitCallbackParam item_func; + item_func.function_name = event["func"]; + if (event.has("param")) + { + item_func.parameter = event["param"]; + } + item_params.on_click = item_func; + if(LLMenuGL* parent_menu = get_parent_menu(response, event)) + { + if(!parent_menu->append(LLUICtrlFactory::create(item_params))) + { + response.error(stringize("Menu item ", std::quoted(event["name"].asString()), " was not added")); + } + } +} + +void LLUIListener::addMenuSeparator(const LLSD&event) const +{ + Response response(LLSD(), event); + if(LLMenuGL* parent_menu = get_parent_menu(response, event)) + { + if(!parent_menu->addSeparator()) + { + response.error("Separator was not added"); + } + } +} diff --git a/indra/newview/lluilistener.h b/indra/newview/lluilistener.h index 0df2afb3fe..cbb5014300 100644 --- a/indra/newview/lluilistener.h +++ b/indra/newview/lluilistener.h @@ -39,12 +39,15 @@ class LLUIListener: public LLEventAPI public: LLUIListener(); -// FIXME These fields are intended to be private, changed here to support very hacky code in llluamanager.cpp -public: +private: void call(const LLSD& event); void getValue(const LLSD&event) const; - private: + void addMenu(const LLSD&event) const; + void addMenuBranch(const LLSD&event) const; + void addMenuItem(const LLSD&event) const; + void addMenuSeparator(const LLSD&event) const; + F64 mLastUntrustedThrottle {0}; F64 mLastMinThrottle {0}; }; diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua index 1eee4657f4..28488ff3e1 100644 --- a/indra/newview/scripts/lua/require/UI.lua +++ b/indra/newview/scripts/lua/require/UI.lua @@ -135,4 +135,32 @@ function UI.snapshot(...) args.op = 'saveSnapshot' return leap.request('LLViewerWindow', args).result end + +-- *************************************************************************** +-- Top menu +-- *************************************************************************** + +function UI.addMenu(...) + local args = mapargs('name,label', ...) + args.op = 'addMenu' + return leap.request('UI', args) +end + +function UI.addMenuBranch(...) + local args = mapargs('name,label,parent_menu', ...) + args.op = 'addMenuBranch' + return leap.request('UI', args) +end + +function UI.addMenuItem(...) + local args = mapargs('name,label,parent_menu,func,param', ...) + args.op = 'addMenuItem' + return leap.request('UI', args) +end + +function UI.addMenuSeparator(...) + local args = mapargs('parent_menu', ...) + args.op = 'addMenuSeparator' + return leap.request('UI', args) +end return UI diff --git a/indra/newview/scripts/lua/test_top_menu.lua b/indra/newview/scripts/lua/test_top_menu.lua new file mode 100644 index 0000000000..780a384c92 --- /dev/null +++ b/indra/newview/scripts/lua/test_top_menu.lua @@ -0,0 +1,34 @@ +UI = require 'UI' + +--Add new drop-down 'LUA Menu' to the Top menu. +local MENU_NAME = "lua_menu" +UI.addMenu{name=MENU_NAME,label="LUA Menu"} + +--Add two new menu items to the 'LUA Menu': 'Debug console' and 'Scripts' +UI.addMenuItem{name="lua_debug",label="Debug console", + param="lua_debug", + func="Floater.ToggleOrBringToFront", + parent_menu=MENU_NAME} + +UI.addMenuItem{name="lua_scripts",label="Scripts", + param="lua_scripts", + func="Floater.ToggleOrBringToFront", + parent_menu=MENU_NAME} + +--Add menu separator to the 'LUA Menu' under added menu items +UI.addMenuSeparator{parent_menu=MENU_NAME} + +--Add two new menu branch 'About...' to the 'LUA Menu' +local BRANCH_NAME = "about_branch" +UI.addMenuBranch{name="about_branch",label="About...",parent_menu=MENU_NAME} + +--Add two new menu items to the 'About...' branch +UI.addMenuItem{name="lua_info",label="Lua...", + param="https://www.lua.org/about.html", + func="Advanced.ShowURL", + parent_menu=BRANCH_NAME} + +UI.addMenuItem{name="lua_info",label="Luau...", + param="https://luau-lang.org/", + func="Advanced.ShowURL", + parent_menu=BRANCH_NAME} -- cgit v1.2.3 From 35ee96709ef2704a2636a11c67d61190dd6bdd50 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 18 Jul 2024 12:55:38 -0400 Subject: Make `LLEventPump::listen()` also accept new `LLAwareListener`. `listen()` still takes `LLEventListener`, a `callable(const LLSD&)`, but now also accepts `LLAwareListener`, a `callable(const LLBoundListener&, const LLSD&)`. This uses `boost::signals2::signal::connect_extended()`, which, when the signal is called, passes to a connected listener the `LLBoundListener` (aka `boost::signals2::connection`) representing its own connection. This allows a listener to disconnect itself when done. Internally, `listen_impl()` now always uses `connect_extended()`. When passed a classic `LLEventListener`, `listen()` wraps it in a lambda that ignores the passed `LLBoundListener`. `listen()` also now accepts `LLVoidListener`, and internally wraps it in a lambda that returns `false` on its behalf. --- indra/llcommon/lleventfilter.cpp | 13 +++--- indra/llcommon/lleventfilter.h | 7 ++-- indra/llcommon/llevents.cpp | 11 +++-- indra/llcommon/llevents.h | 70 +++++++++++++++++++++++++++---- indra/newview/tests/llluamanager_test.cpp | 25 ++++------- 5 files changed, 86 insertions(+), 40 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp index da19946e3b..2b5401e9f7 100644 --- a/indra/llcommon/lleventfilter.cpp +++ b/indra/llcommon/lleventfilter.cpp @@ -365,21 +365,22 @@ bool LLEventLogProxy::post(const LLSD& event) /* override */ } LLBoundListener LLEventLogProxy::listen_impl(const std::string& name, - const LLEventListener& target, + const LLAwareListener& target, const NameList& after, const NameList& before) { LL_DEBUGS("LogProxy") << "LLEventLogProxy('" << getName() << "').listen('" << name << "')" << LL_ENDL; return mPump.listen(name, - [this, name, target](const LLSD& event)->bool - { return listener(name, target, event); }, + [this, name, target](const LLBoundListener& conn, const LLSD& event) + { return listener(conn, name, target, event); }, after, before); } -bool LLEventLogProxy::listener(const std::string& name, - const LLEventListener& target, +bool LLEventLogProxy::listener(const LLBoundListener& conn, + const std::string& name, + const LLAwareListener& target, const LLSD& event) const { auto eventminus = event; @@ -391,7 +392,7 @@ bool LLEventLogProxy::listener(const std::string& name, } std::string hdr{STRINGIZE(getName() << " to " << name << " " << counter)}; LL_INFOS("LogProxy") << hdr << ": " << eventminus << LL_ENDL; - bool result = target(eventminus); + bool result = target(conn, eventminus); LL_INFOS("LogProxy") << hdr << " => " << result << LL_ENDL; return result; } diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h index 9988459aae..de706d72eb 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -459,7 +459,7 @@ public: LLEventLogProxy(LLEventPump& source, const std::string& name, bool tweak=false); /// register a new listener - LLBoundListener listen_impl(const std::string& name, const LLEventListener& target, + LLBoundListener listen_impl(const std::string& name, const LLAwareListener& target, const NameList& after, const NameList& before); /// Post an event to all listeners @@ -469,8 +469,9 @@ private: /// This method intercepts each call to any target listener. We pass it /// the listener name and the caller's intended target listener plus the /// posted LLSD event. - bool listener(const std::string& name, - const LLEventListener& target, + bool listener(const LLBoundListener& conn, + const std::string& name, + const LLAwareListener& target, const LLSD& event) const; LLEventPump& mPump; diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index 5a6e13cb7d..98bd990f31 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -415,7 +415,7 @@ void LLEventPump::reset() //mDeps.clear(); } -LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventListener& listener, +LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLAwareListener& listener, const NameList& after, const NameList& before) { @@ -575,7 +575,7 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL } // Now that newNode has a value that places it appropriately in mSignal, // connect it. - LLBoundListener bound = mSignal->connect(nodePosition, listener); + LLBoundListener bound = mSignal->connect_extended(nodePosition, listener); if (!name.empty()) { // note that we are not tracking anonymous listeners here either. @@ -659,7 +659,7 @@ bool LLEventMailDrop::post(const LLSD& event) } LLBoundListener LLEventMailDrop::listen_impl(const std::string& name, - const LLEventListener& listener, + const LLAwareListener& listener, const NameList& after, const NameList& before) { @@ -668,7 +668,10 @@ LLBoundListener LLEventMailDrop::listen_impl(const std::string& name, // Remove any that this listener consumes -- Effective STL, Item 9. for (auto hi(mEventHistory.begin()), hend(mEventHistory.end()); hi != hend; ) { - if (listener(*hi)) + // We don't actually have an LLBoundListener in hand, and we won't + // until the base-class listen_impl() call below. Pass an empty + // instance. + if (listener({}, *hi)) { // new listener consumed this event, erase it hi = mEventHistory.erase(hi); diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index d0686bd8b5..8ef3a5af95 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -32,12 +32,13 @@ #if ! defined(LL_LLEVENTS_H) #define LL_LLEVENTS_H -#include +#include +#include #include #include +#include +#include #include -#include -#include #if LL_WINDOWS #pragma warning (push) #pragma warning (disable : 4263) // boost::signals2::expired_slot::what() has const mismatch @@ -137,6 +138,10 @@ typedef boost::signals2::signal LL /// Methods that forward listeners (e.g. constructed with /// boost::bind()) should accept (const LLEventListener&) typedef LLStandardSignal::slot_type LLEventListener; +/// Support a listener accepting (const LLBoundListener&, const LLSD&). +/// Note that LLBoundListener::disconnect() is a const method: this feature is +/// specifically intended to allow a listener to disconnect itself when done. +typedef LLStandardSignal::extended_slot_type LLAwareListener; /// Accept a void listener too typedef std::function LLVoidListener; /// Result of registering a listener, supports connected(), @@ -522,18 +527,37 @@ public: * call, allows us to optimize away the second and subsequent dependency * sorts. * - * If name is set to LLEventPump::ANONYMOUS listen will bypass the entire + * If name is set to LLEventPump::ANONYMOUS, listen() will bypass the entire * dependency and ordering calculation. In this case, it is critical that * the result be assigned to a LLTempBoundListener or the listener is - * manually disconnected when no longer needed since there will be no + * manually disconnected when no longer needed, since there will be no * way to later find and disconnect this listener manually. */ + template LLBoundListener listen(const std::string& name, - const LLEventListener& listener, + LISTENER&& listener, const NameList& after=NameList(), const NameList& before=NameList()) { - return listen_impl(name, listener, after, before); + if constexpr (std::is_invocable_v) + { + // wrap classic LLEventListener in LLAwareListener lambda + return listenb( + name, + [listener=std::move(listener)] + (const LLBoundListener&, const LLSD& event) + { + return listener(event); + }, + after, + before); + } + else + { + static_assert(std::is_invocable_v, + "LLEventPump::listen() listener has bad parameter signature"); + return listenb(name, std::forward(listener), after, before); + } } /// Get the LLBoundListener associated with the passed name (dummy @@ -579,7 +603,35 @@ private: LLMutex mConnectionListMutex; protected: - virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&, + template + LLBoundListener listenb(const std::string& name, + LISTENER&& listener, + const NameList& after=NameList(), + const NameList& before=NameList()) + { + using result_t = std::decay_t; + if constexpr (std::is_same_v) + { + return listen_impl(name, std::forward(listener), after, before); + } + else + { + static_assert(std::is_same_v, + "LLEventPump::listen() listener has bad return type"); + // wrap void listener in one that returns bool + return listen_impl( + name, + [listener=std::move(listener)] + (const LLBoundListener& conn, const LLSD& event) + { + listener(conn, event); + return false; + }, + after, + before); + } + } + virtual LLBoundListener listen_impl(const std::string& name, const LLAwareListener&, const NameList& after, const NameList& before); @@ -654,7 +706,7 @@ public: void discard(); protected: - virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&, + virtual LLBoundListener listen_impl(const std::string& name, const LLAwareListener&, const NameList& after, const NameList& before) override; diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index 26a4ac95e3..3209d93d39 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -42,17 +42,6 @@ public: LLControlGroup gSavedSettings("Global"); -template -auto listener(CALLABLE&& callable) -{ - return [callable=std::forward(callable)] - (const LLSD& data) - { - callable(data); - return false; - }; -} - /***************************************************************************** * TUT *****************************************************************************/ @@ -138,7 +127,7 @@ namespace tut { LLSD fromlua; LLStreamListener pump("testpump", - listener([&fromlua](const LLSD& data){ fromlua = data; })); + [&fromlua](const LLSD& data){ fromlua = data; }); const std::string lua(stringize( "data = ", construct, "\n" "LL.post_on('testpump', data)\n" @@ -167,8 +156,8 @@ namespace tut set_test_name("test post_on(), get_event_pumps(), get_event_next()"); StringVec posts; LLStreamListener pump("testpump", - listener([&posts](const LLSD& data) - { posts.push_back(data.asString()); })); + [&posts](const LLSD& data) + { posts.push_back(data.asString()); }); const std::string lua( "-- test post_on,get_event_pumps,get_event_next\n" "LL.post_on('testpump', 'entry')\n" @@ -346,11 +335,11 @@ namespace tut LLStreamListener pump( "echo", - listener([](const LLSD& data) + [](const LLSD& data) { LL_DEBUGS("Lua") << "echo pump got: " << data << LL_ENDL; sendReply(data, data); - })); + }); auto [count, result] = LLLUAmanager::waitScriptLine(lua); ensure_equals("Lua script didn't return item", count, 1); @@ -424,11 +413,11 @@ namespace tut LLSD requests; LLStreamListener pump( "testpump", - listener([&requests](const LLSD& data) + [&requests](const LLSD& data) { LL_DEBUGS("Lua") << "testpump got: " << data << LL_ENDL; requests.append(data); - })); + }); auto future = LLLUAmanager::startScriptLine(lua); // LuaState::expr() periodically interrupts a running chunk to ensure -- cgit v1.2.3 From f2f0fa7fd0efc221f1358dd4e440b5d51a5fb8b4 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 18 Jul 2024 13:29:34 -0400 Subject: Ditch `LLEventTrackable` aka `boost::signals2::trackable`. Remove documented `LLEventPump` support for `LLEventTrackable`. That claimed support was always a little bit magical/fragile. IF: * a class included `LLEventTrackable` as a base class AND * an instance of that class was managed by `boost::shared_ptr` AND * you passed one of that class's methods and the `boost::shared_ptr` specifically to `boost::bind()` AND * the resulting `boost::bind()` object was passed into `LLEventPump::listen()` THEN the promise was that on destruction of that object, that listener would automatically be disconnected -- instead of leaving a dangling pointer bound into the `LLEventPump`, causing a crash on the next `LLEventPump::post()` call. The only existing code in the viewer code base that exercised `LLEventTrackable` functionality was in test programs. When the viewer calls `LLEventPump::listen()`, it typically stores the resulting connection object in an `LLTempBoundListener` variable, which guarantees disconnection on destruction of that variable. The fact that `LLEventTrackable` support is specific to `boost::bind()`, that it silently fails to keep its promise with `std::bind()` or a lambda or any other form of C++ callable, makes it untrustworthy for new code. Note that the code base still uses `boost::signals2::trackable` for other `boost::signals2::signal` instances not associated with `LLEventPump`. We are not changing those at this time. --- indra/llcommon/llevents.h | 45 +---------------- indra/llui/llnotifications.h | 2 +- indra/llui/llnotificationslistener.cpp | 16 +++--- indra/newview/llspeakers.h | 2 +- indra/test/llevents_tut.cpp | 58 +--------------------- .../viewer_components/login/tests/lllogin_test.cpp | 4 +- 6 files changed, 15 insertions(+), 112 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 8ef3a5af95..abc25ba400 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -363,57 +363,14 @@ testable: InstanceTypes mTypes; }; -/***************************************************************************** -* LLEventTrackable -*****************************************************************************/ -/** - * LLEventTrackable wraps boost::signals2::trackable, which resembles - * boost::trackable. Derive your listener class from LLEventTrackable instead, - * and use something like - * LLEventPump::listen(boost::bind(&YourTrackableSubclass::method, - * instance, _1)). This will implicitly disconnect when the object - * referenced by @c instance is destroyed. - * - * @note - * LLEventTrackable doesn't address a couple of cases: - * * Object destroyed during call - * - You enter a slot call in thread A. - * - Thread B destroys the object, which of course disconnects it from any - * future slot calls. - * - Thread A's call uses 'this', which now refers to a defunct object. - * Undefined behavior results. - * * Call during destruction - * - @c MySubclass is derived from LLEventTrackable. - * - @c MySubclass registers one of its own methods using - * LLEventPump::listen(). - * - The @c MySubclass object begins destruction. ~MySubclass() - * runs, destroying state specific to the subclass. (For instance, a - * Foo* data member is deleted but not zeroed.) - * - The listening method will not be disconnected until - * ~LLEventTrackable() runs. - * - Before we get there, another thread posts data to the @c LLEventPump - * instance, calling the @c MySubclass method. - * - The method in question relies on valid @c MySubclass state. (For - * instance, it attempts to dereference the Foo* pointer that was - * deleted but not zeroed.) - * - Undefined behavior results. - */ -typedef boost::signals2::trackable LLEventTrackable; - /***************************************************************************** * LLEventPump *****************************************************************************/ /** * LLEventPump is the base class interface through which we access the * concrete subclasses such as LLEventStream. - * - * @NOTE - * LLEventPump derives from LLEventTrackable so that when you "chain" - * LLEventPump instances together, they will automatically disconnect on - * destruction. Please see LLEventTrackable documentation for situations in - * which this may be perilous across threads. */ -class LL_COMMON_API LLEventPump: public LLEventTrackable +class LL_COMMON_API LLEventPump { public: static const std::string ANONYMOUS; // constant for anonymous listeners. diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index ab4f009a80..206c521592 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -734,7 +734,7 @@ typedef std::multimap LLNotificationMap; // all of the built-in tests should attach to the "Visible" channel // class LLNotificationChannelBase : - public LLEventTrackable, + public boost::signals2::trackable, public LLRefCount { LOG_CLASS(LLNotificationChannelBase); diff --git a/indra/llui/llnotificationslistener.cpp b/indra/llui/llnotificationslistener.cpp index ace9e37e25..2ad1689a45 100644 --- a/indra/llui/llnotificationslistener.cpp +++ b/indra/llui/llnotificationslistener.cpp @@ -204,7 +204,7 @@ void LLNotificationsListener::ignore(const LLSD& params) const } } -class LLNotificationsListener::Forwarder: public LLEventTrackable +class LLNotificationsListener::Forwarder: public boost::signals2::trackable { LOG_CLASS(LLNotificationsListener::Forwarder); public: @@ -213,8 +213,10 @@ public: mRespond(false) { // Connect to the specified channel on construction. Because - // LLEventTrackable is a base, we should automatically disconnect when - // destroyed. + // boost::signals2::trackable is a base, because we use boost::bind() + // below, and because connectPassedFilter() directly calls + // boost::signals2::signal::connect(), we should automatically + // disconnect when destroyed. LLNotificationChannelPtr channelptr(llnotifications.getChannel(channel)); if (channelptr) { @@ -252,10 +254,10 @@ void LLNotificationsListener::forward(const LLSD& params) if (! forward) { // This is a request to stop forwarding notifications on the specified - // channel. The rest of the params don't matter. - // Because mForwarders contains scoped_ptrs, erasing the map entry - // DOES delete the heap Forwarder object. Because Forwarder derives - // from LLEventTrackable, destroying it disconnects it from the + // channel. The rest of the params don't matter. Because mForwarders + // contains scoped_ptrs, erasing the map entry DOES delete the heap + // Forwarder object. Because Forwarder derives from + // boost::signals2::trackable, destroying it disconnects it from the // channel. mForwarders.erase(channel); return; diff --git a/indra/newview/llspeakers.h b/indra/newview/llspeakers.h index 234de42953..d3304dba59 100644 --- a/indra/newview/llspeakers.h +++ b/indra/newview/llspeakers.h @@ -37,7 +37,7 @@ class LLSpeakerMgr; class LLAvatarName; // data for a given participant in a voice channel -class LLSpeaker : public LLRefCount, public LLOldEvents::LLObservable, public LLHandleProvider, public boost::signals2::trackable +class LLSpeaker : public LLRefCount, public LLOldEvents::LLObservable, public LLHandleProvider { public: typedef enum e_speaker_type diff --git a/indra/test/llevents_tut.cpp b/indra/test/llevents_tut.cpp index 875ca9ad89..f9cc99203b 100644 --- a/indra/test/llevents_tut.cpp +++ b/indra/test/llevents_tut.cpp @@ -429,7 +429,7 @@ void events_object::test<9>() { set_test_name("listen(boost::bind(...TempListener...))"); // listen() can't do anything about a plain TempListener instance: - // it's not managed with shared_ptr, nor is it an LLEventTrackable subclass + // it's not managed with shared_ptr bool live = false; LLEventPump& heaptest(pumps.obtain("heaptest")); LLBoundListener connection; @@ -453,60 +453,4 @@ void events_object::test<9>() heaptest.stopListening("temp"); } -class TempTrackableListener: public TempListener, public LLEventTrackable -{ -public: - TempTrackableListener(const std::string& name, bool& liveFlag): - TempListener(name, liveFlag) - {} -}; - -template<> template<> -void events_object::test<10>() -{ - set_test_name("listen(boost::bind(...TempTrackableListener ref...))"); - bool live = false; - LLEventPump& heaptest(pumps.obtain("heaptest")); - LLBoundListener connection; - { - TempTrackableListener tempListener("temp", live); - ensure("TempTrackableListener constructed", live); - connection = heaptest.listen(tempListener.getName(), - boost::bind(&TempTrackableListener::call, - boost::ref(tempListener), _1)); - heaptest.post(1); - check_listener("received", tempListener, 1); - } // presumably this will make tempListener go away? - // verify that - ensure("TempTrackableListener destroyed", ! live); - ensure("implicit disconnect", ! connection.connected()); - // now just make sure we don't blow up trying to access a freed object! - heaptest.post(2); -} - -template<> template<> -void events_object::test<11>() -{ - set_test_name("listen(boost::bind(...TempTrackableListener pointer...))"); - bool live = false; - LLEventPump& heaptest(pumps.obtain("heaptest")); - LLBoundListener connection; - { - TempTrackableListener* newListener(new TempTrackableListener("temp", live)); - ensure("TempTrackableListener constructed", live); - connection = heaptest.listen(newListener->getName(), - boost::bind(&TempTrackableListener::call, - newListener, _1)); - heaptest.post(1); - check_listener("received", *newListener, 1); - // explicitly destroy newListener - delete newListener; - } - // verify that - ensure("TempTrackableListener destroyed", ! live); - ensure("implicit disconnect", ! connection.connected()); - // now just make sure we don't blow up trying to access a freed object! - heaptest.post(2); -} - } // namespace tut diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp index 8aea3b37aa..f051f8c67f 100644 --- a/indra/viewer_components/login/tests/lllogin_test.cpp +++ b/indra/viewer_components/login/tests/lllogin_test.cpp @@ -66,7 +66,7 @@ * Helper classes *****************************************************************************/ // This is a listener to receive results from lllogin. -class LoginListener: public LLEventTrackable +class LoginListener { std::string mName; LLSD mLastEvent; @@ -137,7 +137,7 @@ public: } }; -class LLXMLRPCListener: public LLEventTrackable +class LLXMLRPCListener { std::string mName; LLSD mEvent; -- cgit v1.2.3 From cd64842e338fb216f0772e371278c56b921f6f87 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 18 Jul 2024 14:24:36 -0400 Subject: Improve viewer's defense against `LLEventAPI` failures. `LLEventAPI` is specifically intended to allow a LEAP plugin, or a Lua script, to access certain viewer functionality. Errors in external code like that cannot be addressed during viewer development. Any code path that allows external code in any form to crash the viewer opens up a potential abuse vector, if a trusting user runs external code from an untrustworthy source. `LLDispatchListener` reports exceptions back to its invoker, if the invoker provides a "reply" `LLEventPump` name. Absent "reply", though, `LLDispatchListener` is documented to let any such exception propagate. That behavior may be okay for internal use, but in the case of the `LLEventAPI` subclass, it veers into the abuse scenario described above. Make `LLEventAPI` ensure that any exception propagating from `LLDispatchListener` is caught and logged, but not propagated. Also enrich error reporting for the "batch" `LLDispatchListener` operations. --- indra/llcommon/lleventapi.cpp | 20 ++++++++++++++++++++ indra/llcommon/lleventapi.h | 1 + indra/llcommon/lleventdispatcher.cpp | 15 +++++++++------ indra/llcommon/lleventdispatcher.h | 4 +++- 4 files changed, 33 insertions(+), 7 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lleventapi.cpp b/indra/llcommon/lleventapi.cpp index 8b724256b8..4672371b4f 100644 --- a/indra/llcommon/lleventapi.cpp +++ b/indra/llcommon/lleventapi.cpp @@ -55,6 +55,26 @@ LLEventAPI::~LLEventAPI() { } +bool LLEventAPI::process(const LLSD& event) const +{ + // LLDispatchListener is documented to let DispatchError propagate if the + // incoming request has no "reply" key. That may be fine for internal-only + // use, but LLEventAPI opens the door for external requests. It should NOT + // be possible for any external requester to crash the viewer with an + // unhandled exception, especially not by something as simple as omitting + // the "reply" key. + try + { + return LLDispatchListener::process(event); + } + catch (const std::exception& err) + { + // log the exception, but otherwise ignore it + LL_WARNS("LLEventAPI") << LLError::Log::classname(err) << ": " << err.what() << LL_ENDL; + return false; + } +} + LLEventAPI::Response::Response(const LLSD& seed, const LLSD& request, const LLSD::String& replyKey): mResp(seed), mReq(request), diff --git a/indra/llcommon/lleventapi.h b/indra/llcommon/lleventapi.h index 3ae820db51..da7c58e6f0 100644 --- a/indra/llcommon/lleventapi.h +++ b/indra/llcommon/lleventapi.h @@ -157,6 +157,7 @@ protected: LLEventAPI(const LL::LazyEventAPIParams&); private: + bool process(const LLSD& event) const override; std::string mDesc; }; diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 9dd6864cff..12b966fadf 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -800,7 +800,7 @@ void LLDispatchListener::call_one(const LLSD& name, const LLSD& event) const { result = (*this)(event); } - catch (const DispatchError& err) + catch (const std::exception& err) { if (! event.has(mReplyKey)) { @@ -810,7 +810,10 @@ void LLDispatchListener::call_one(const LLSD& name, const LLSD& event) const // Here there was an error and the incoming event has mReplyKey. Reply // with a map containing an "error" key explaining the problem. - return reply(llsd::map("error", err.what()), event); + return reply(llsd::map("error", + stringize(LLError::Log::classname(err), + ": ", err.what())), + event); } // We seem to have gotten a valid result. But we don't know whether the @@ -846,7 +849,7 @@ void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const { // in case of errors, tell user the dispatch key, the fact that // we're processing a request map and the current key in that map - SetState(this, '[', key, '[', name, "]]"); + SetState transient(this, '[', key, '[', name, "]]"); // With this form, capture return value even if undefined: // presence of the key in the response map can be used to detect // which request keys succeeded. @@ -869,7 +872,7 @@ void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const if (! event.has(mReplyKey)) { // can't send reply, throw - sCallFail(error); + callFail(error); } else { @@ -923,7 +926,7 @@ void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) con // in case of errors, tell user the dispatch key, the fact that // we're processing a request array, the current entry in that // array and the corresponding callable name - SetState(this, '[', key, '[', i, "]=", name, ']'); + SetState transient(this, '[', key, '[', i, "]=", name, ']'); // With this form, capture return value even if undefined results.append((*this)(name, args)); } @@ -944,7 +947,7 @@ void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) con if (! event.has(mReplyKey)) { // can't send reply, throw - sCallFail(error); + callFail(error); } else { diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 5adaa3ebae..698412fdb4 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -853,8 +853,10 @@ public: std::string getPumpName() const { return getName(); } +protected: + virtual bool process(const LLSD& event) const; + private: - bool process(const LLSD& event) const; void call_one(const LLSD& name, const LLSD& event) const; void call_map(const LLSD& reqmap, const LLSD& event) const; void call_array(const LLSD& reqarray, const LLSD& event) const; -- cgit v1.2.3 From b79bf898c63d06486c978cbeecbe533e5872b56c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 18 Jul 2024 14:42:26 -0400 Subject: Guarantee that the "login" LLEventPump has exactly that name. We used to allow "tweaking" the name. Don't. --- indra/viewer_components/login/lllogin.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp index 2a0468f3ad..dfaaff21c5 100644 --- a/indra/viewer_components/login/lllogin.cpp +++ b/indra/viewer_components/login/lllogin.cpp @@ -53,7 +53,9 @@ class LLLogin::Impl { public: Impl(): - mPump("login", true) // Create the module's event pump with a tweaked (unique) name. + // Create the module's event pump, and do not tweak the name. Multiple + // parties depend on this LLEventPump having exactly the name "login". + mPump("login", false) { mValidAuthResponse["status"] = LLSD(); mValidAuthResponse["errorcode"] = LLSD(); -- cgit v1.2.3 From decc2d3aa5ba8dc583dae5396a5ae8ca738412dd Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Thu, 25 Jul 2024 18:15:24 +0300 Subject: Lua api for Follow Camera control --- indra/newview/llagentlistener.cpp | 74 +++++-- indra/newview/llagentlistener.h | 5 +- .../scripts/lua/luafloater_camera_control.xml | 244 +++++++++++++++++++++ indra/newview/scripts/lua/require/LLAgent.lua | 28 +++ indra/newview/scripts/lua/test_camera_control.lua | 52 +++++ 5 files changed, 379 insertions(+), 24 deletions(-) create mode 100644 indra/newview/scripts/lua/luafloater_camera_control.xml create mode 100644 indra/newview/scripts/lua/require/LLAgent.lua create mode 100644 indra/newview/scripts/lua/test_camera_control.lua (limited to 'indra') diff --git a/indra/newview/llagentlistener.cpp b/indra/newview/llagentlistener.cpp index 54998f3945..38a9d58c1f 100644 --- a/indra/newview/llagentlistener.cpp +++ b/indra/newview/llagentlistener.cpp @@ -31,6 +31,7 @@ #include "llagentlistener.h" #include "llagent.h" +#include "llagentcamera.h" #include "llvoavatar.h" #include "llcommandhandler.h" #include "llslurl.h" @@ -69,13 +70,6 @@ LLAgentListener::LLAgentListener(LLAgent &agent) add("resetAxes", "Set the agent to a fixed orientation (optionally specify [\"lookat\"] = array of [x, y, z])", &LLAgentListener::resetAxes); - add("getAxes", - "Obsolete - use getPosition instead\n" - "Send information about the agent's orientation on [\"reply\"]:\n" - "[\"euler\"]: map of {roll, pitch, yaw}\n" - "[\"quat\"]: array of [x, y, z, w] quaternion values", - &LLAgentListener::getAxes, - LLSDMap("reply", LLSD())); add("getPosition", "Send information about the agent's position and orientation on [\"reply\"]:\n" "[\"region\"]: array of region {x, y, z} position\n" @@ -138,6 +132,21 @@ LLAgentListener::LLAgentListener(LLAgent &agent) "[\"contrib\"]: user's land contribution to this group\n", &LLAgentListener::getGroups, LLSDMap("reply", LLSD())); + add("setCameraParams", + "Set Follow camera params, and then activate it:\n" + "[\"camera_pos\"]: vector3\n" + "[\"focus_pos\"]: vector3\n" + "[\"focus_offset\"]: vector3\n" + "[\"camera_locked\"]: boolean\n" + "[\"focus_locked\"]: boolean", + &LLAgentListener::setFollowCamParams); + add("setFollowCamActive", + "Set Follow camera active or deactivate it using boolean [\"active\"]", + &LLAgentListener::setFollowCamActive, + llsd::map("active", LLSD())); + add("removeCameraParams", + "Reset Follow camera params", + &LLAgentListener::removeFollowCamParams); } void LLAgentListener::requestTeleport(LLSD const & event_data) const @@ -296,22 +305,6 @@ void LLAgentListener::resetAxes(const LLSD& event_data) const } } -void LLAgentListener::getAxes(const LLSD& event_data) const -{ - LLQuaternion quat(mAgent.getQuat()); - F32 roll, pitch, yaw; - quat.getEulerAngles(&roll, &pitch, &yaw); - // The official query API for LLQuaternion's [x, y, z, w] values is its - // public member mQ... - LLSD reply = LLSD::emptyMap(); - reply["quat"] = llsd_copy_array(boost::begin(quat.mQ), boost::end(quat.mQ)); - reply["euler"] = LLSD::emptyMap(); - reply["euler"]["roll"] = roll; - reply["euler"]["pitch"] = pitch; - reply["euler"]["yaw"] = yaw; - sendReply(reply, event_data); -} - void LLAgentListener::getPosition(const LLSD& event_data) const { F32 roll, pitch, yaw; @@ -519,3 +512,38 @@ void LLAgentListener::getGroups(const LLSD& event) const } sendReply(LLSDMap("groups", reply), event); } + +void LLAgentListener::setFollowCamParams(const LLSD& event) const +{ + if (event.has("camera_pos")) + { + LLFollowCamMgr::getInstance()->setPosition(gAgentID, LLVector3(event["camera_pos"])); + } + if (event.has("focus_pos")) + { + LLFollowCamMgr::getInstance()->setFocus(gAgentID, LLVector3(event["focus_pos"])); + } + if (event.has("focus_offset")) + { + LLFollowCamMgr::getInstance()->setFocusOffset(gAgentID, LLVector3(event["focus_offset"])); + } + if (event.has("camera_locked")) + { + LLFollowCamMgr::getInstance()->setPositionLocked(gAgentID, event["camera_locked"]); + } + if (event.has("focus_locked")) + { + LLFollowCamMgr::getInstance()->setFocusLocked(gAgentID, event["focus_locked"]); + } + LLFollowCamMgr::getInstance()->setCameraActive(gAgentID, true); +} + +void LLAgentListener::setFollowCamActive(LLSD const & event) const +{ + LLFollowCamMgr::getInstance()->setCameraActive(gAgentID, event["active"]); +} + +void LLAgentListener::removeFollowCamParams(LLSD const & event) const +{ + LLFollowCamMgr::getInstance()->removeFollowCamParams(gAgentID); +} diff --git a/indra/newview/llagentlistener.h b/indra/newview/llagentlistener.h index c544d089ce..2a24de3f52 100644 --- a/indra/newview/llagentlistener.h +++ b/indra/newview/llagentlistener.h @@ -48,7 +48,6 @@ private: void requestStand(LLSD const & event_data) const; void requestTouch(LLSD const & event_data) const; void resetAxes(const LLSD& event_data) const; - void getAxes(const LLSD& event_data) const; void getGroups(const LLSD& event) const; void getPosition(const LLSD& event_data) const; void startAutoPilot(const LLSD& event_data); @@ -58,6 +57,10 @@ private: void stopAutoPilot(const LLSD& event_data) const; void lookAt(LLSD const & event_data) const; + void setFollowCamParams(LLSD const & event_data) const; + void setFollowCamActive(LLSD const & event_data) const; + void removeFollowCamParams(LLSD const & event_data) const; + LLViewerObject * findObjectClosestTo( const LLVector3 & position ) const; private: diff --git a/indra/newview/scripts/lua/luafloater_camera_control.xml b/indra/newview/scripts/lua/luafloater_camera_control.xml new file mode 100644 index 0000000000..0601a363e5 --- /dev/null +++ b/indra/newview/scripts/lua/luafloater_camera_control.xml @@ -0,0 +1,244 @@ + + + + Camera position: + + + + + + + + + + Focus position: + + + + + + + + + + Lock: + + + + + + + diff --git a/indra/newview/scripts/lua/require/LLAgent.lua b/indra/newview/scripts/lua/require/LLAgent.lua new file mode 100644 index 0000000000..7c6a842555 --- /dev/null +++ b/indra/newview/scripts/lua/require/LLAgent.lua @@ -0,0 +1,28 @@ +local leap = require 'leap' +local mapargs = require 'mapargs' + +local LLAgent = {} + +function LLAgent.getRegionPosition() + return leap.request('LLAgent', {op = 'getPosition'}).region +end + +function LLAgent.getGlobalPosition() + return leap.request('LLAgent', {op = 'getPosition'}).global +end + +function LLAgent.setCamera(...) + local args = mapargs('camera_pos,focus_pos,focus_offset,camera_locked,focus_locked', ...) + args.op = 'setCameraParams' + leap.send('LLAgent', args) +end + +function LLAgent.setFollowCamActive(active) + leap.send('LLAgent', {op = 'setFollowCamActive', active = active}) +end + +function LLAgent.removeCamParams() + leap.send('LLAgent', {op = 'removeCameraParams'}) +end + +return LLAgent diff --git a/indra/newview/scripts/lua/test_camera_control.lua b/indra/newview/scripts/lua/test_camera_control.lua new file mode 100644 index 0000000000..db76201932 --- /dev/null +++ b/indra/newview/scripts/lua/test_camera_control.lua @@ -0,0 +1,52 @@ +local Floater = require 'Floater' +local LLAgent = require 'LLAgent' +local leap = require 'leap' +local startup = require 'startup' +local inspect = require 'inspect' + +local flt = Floater('luafloater_camera_control.xml') + +function getValue(ctrl_name) + return flt:request({action="get_value", ctrl_name=ctrl_name}).value +end + +function setValue(ctrl_name, value) + flt:post({action="set_value", ctrl_name=ctrl_name, value=value}) +end + +function flt:commit_update_btn(event_data) + lock_focus = getValue('lock_focus_ctrl') + lock_camera = self:request({action="get_value", ctrl_name='lock_camera_ctrl'}).value + + local camera_pos = {getValue('cam_x'),getValue('cam_y'),getValue('cam_z')} + local focus_pos = {getValue('focus_x'),getValue('focus_y'),getValue('focus_z')} + + + LLAgent.setCamera{camera_pos=camera_pos,focus_pos=focus_pos, + focus_locked = lock_focus,camera_locked = lock_camera} + + self:post({action="add_text", ctrl_name="events_editor", + value = {'Updating FollowCam params', 'camera_pos:', camera_pos, 'focus_pos:', focus_pos, 'lock_focus:', lock_focus, 'lock_camera:', lock_camera}}) +end + +function flt:commit_agent_cam_btn(event_data) + agent_pos = LLAgent.getRegionPosition() + setValue('cam_x', math.floor(agent_pos[1])) + setValue('cam_y', math.floor(agent_pos[2])) + setValue('cam_z', math.floor(agent_pos[3])) +end + +function flt:commit_agent_focus_btn(event_data) + agent_pos = LLAgent.getRegionPosition() + setValue('focus_x', math.floor(agent_pos[1])) + setValue('focus_y', math.floor(agent_pos[2])) + setValue('focus_z', math.floor(agent_pos[3])) +end + +function flt:commit_reset_btn(event_data) + LLAgent.removeCamParams() + LLAgent.setFollowCamActive(false) +end + +startup.wait('STATE_LOGIN_WAIT') +flt:show() -- cgit v1.2.3 From 4edcebdb31b7d49faf94b60a66c9921e90e23899 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Thu, 25 Jul 2024 18:29:06 +0300 Subject: Script clean up --- indra/newview/scripts/lua/test_camera_control.lua | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/test_camera_control.lua b/indra/newview/scripts/lua/test_camera_control.lua index db76201932..e7ad69b473 100644 --- a/indra/newview/scripts/lua/test_camera_control.lua +++ b/indra/newview/scripts/lua/test_camera_control.lua @@ -1,8 +1,6 @@ local Floater = require 'Floater' local LLAgent = require 'LLAgent' -local leap = require 'leap' local startup = require 'startup' -local inspect = require 'inspect' local flt = Floater('luafloater_camera_control.xml') @@ -16,17 +14,16 @@ end function flt:commit_update_btn(event_data) lock_focus = getValue('lock_focus_ctrl') - lock_camera = self:request({action="get_value", ctrl_name='lock_camera_ctrl'}).value + lock_camera = getValue('lock_camera_ctrl') + local camera_pos = {getValue('cam_x'), getValue('cam_y'), getValue('cam_z')} + local focus_pos = {getValue('focus_x'), getValue('focus_y'), getValue('focus_z')} - local camera_pos = {getValue('cam_x'),getValue('cam_y'),getValue('cam_z')} - local focus_pos = {getValue('focus_x'),getValue('focus_y'),getValue('focus_z')} - - - LLAgent.setCamera{camera_pos=camera_pos,focus_pos=focus_pos, - focus_locked = lock_focus,camera_locked = lock_camera} + LLAgent.setCamera{camera_pos=camera_pos, focus_pos=focus_pos, + focus_locked=lock_focus, camera_locked=lock_camera} self:post({action="add_text", ctrl_name="events_editor", - value = {'Updating FollowCam params', 'camera_pos:', camera_pos, 'focus_pos:', focus_pos, 'lock_focus:', lock_focus, 'lock_camera:', lock_camera}}) + value = {'Updating FollowCam params', 'camera_pos:', camera_pos, 'focus_pos:', focus_pos, + 'lock_focus:', lock_focus, 'lock_camera:', lock_camera}}) end function flt:commit_agent_cam_btn(event_data) -- cgit v1.2.3 From 41ea8a61c247d915ebe53436e9cfc999a712b692 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 26 Jul 2024 15:55:17 +0300 Subject: Add api for more script camera params --- indra/newview/llagentlistener.cpp | 54 ++++++++++++++++++++++++--- indra/newview/scripts/lua/require/LLAgent.lua | 19 +++++++++- 2 files changed, 66 insertions(+), 7 deletions(-) (limited to 'indra') diff --git a/indra/newview/llagentlistener.cpp b/indra/newview/llagentlistener.cpp index 38a9d58c1f..045367dbdd 100644 --- a/indra/newview/llagentlistener.cpp +++ b/indra/newview/llagentlistener.cpp @@ -132,16 +132,25 @@ LLAgentListener::LLAgentListener(LLAgent &agent) "[\"contrib\"]: user's land contribution to this group\n", &LLAgentListener::getGroups, LLSDMap("reply", LLSD())); + //camera params are similar to LSL, see https://wiki.secondlife.com/wiki/LlSetCameraParams add("setCameraParams", "Set Follow camera params, and then activate it:\n" - "[\"camera_pos\"]: vector3\n" - "[\"focus_pos\"]: vector3\n" - "[\"focus_offset\"]: vector3\n" - "[\"camera_locked\"]: boolean\n" - "[\"focus_locked\"]: boolean", + "[\"camera_pos\"]: vector3, camera position in region coordinates\n" + "[\"focus_pos\"]: vector3, what the camera is aimed at (in region coordinates)\n" + "[\"focus_offset\"]: vector3, adjusts the camera focus position relative to the target, default is (1, 0, 0)\n" + "[\"distance\"]: float (meters), distance the camera wants to be from its target, default is 3\n" + "[\"focus_threshold\"]: float (meters), sets the radius of a sphere around the camera's target position within which its focus is not affected by target motion, default is 1\n" + "[\"camera_threshold\"]: float (meters), sets the radius of a sphere around the camera's ideal position within which it is not affected by target motion, default is 1\n" + "[\"focus_lag\"]: float (seconds), how much the camera lags as it tries to aim towards the target, default is 0.1\n" + "[\"camera_lag\"]: float (seconds), how much the camera lags as it tries to move towards its 'ideal' position, default is 0.1\n" + "[\"camera_pitch\"]: float (degrees), adjusts the angular amount that the camera aims straight ahead vs. straight down, maintaining the same distance, default is 0\n" + "[\"behindness_angle\"]: float (degrees), sets the angle in degrees within which the camera is not constrained by changes in target rotation, default is 10\n" + "[\"behindness_lag\"]: float (seconds), sets how strongly the camera is forced to stay behind the target if outside of behindness angle, default is 0\n" + "[\"camera_locked\"]: bool, locks the camera position so it will not move\n" + "[\"focus_locked\"]: bool, locks the camera focus so it will not move", &LLAgentListener::setFollowCamParams); add("setFollowCamActive", - "Set Follow camera active or deactivate it using boolean [\"active\"]", + "Turns on or off scripted control of the camera using boolean [\"active\"]", &LLAgentListener::setFollowCamActive, llsd::map("active", LLSD())); add("removeCameraParams", @@ -535,6 +544,39 @@ void LLAgentListener::setFollowCamParams(const LLSD& event) const { LLFollowCamMgr::getInstance()->setFocusLocked(gAgentID, event["focus_locked"]); } + if (event.has("distance")) + { + LLFollowCamMgr::getInstance()->setDistance(gAgentID, event["distance"].asReal()); + } + if (event.has("focus_threshold")) + { + LLFollowCamMgr::getInstance()->setFocusThreshold(gAgentID, event["focus_threshold"].asReal()); + } + if (event.has("camera_threshold")) + { + LLFollowCamMgr::getInstance()->setPositionThreshold(gAgentID, event["camera_threshold"].asReal()); + } + if (event.has("focus_lag")) + { + LLFollowCamMgr::getInstance()->setFocusLag(gAgentID, event["focus_lag"].asReal()); + } + if (event.has("camera_lag")) + { + LLFollowCamMgr::getInstance()->setPositionLag(gAgentID, event["camera_lag"].asReal()); + } + if (event.has("camera_pitch")) + { + LLFollowCamMgr::getInstance()->setPitch(gAgentID, event["camera_pitch"].asReal()); + } + if (event.has("behindness_lag")) + { + LLFollowCamMgr::getInstance()->setBehindnessLag(gAgentID, event["behindness_lag"].asReal()); + } + if (event.has("behindness_angle")) + { + LLFollowCamMgr::getInstance()->setBehindnessAngle(gAgentID, event["behindness_angle"].asReal()); + } + LLFollowCamMgr::getInstance()->setCameraActive(gAgentID, true); } diff --git a/indra/newview/scripts/lua/require/LLAgent.lua b/indra/newview/scripts/lua/require/LLAgent.lua index 7c6a842555..bc9a6b23a0 100644 --- a/indra/newview/scripts/lua/require/LLAgent.lua +++ b/indra/newview/scripts/lua/require/LLAgent.lua @@ -11,8 +11,25 @@ function LLAgent.getGlobalPosition() return leap.request('LLAgent', {op = 'getPosition'}).global end +-- Use LL.leaphelp('LLAgent') and see 'setCameraParams' to get more info about params +-- -- TYPE -- DEFAULT -- RANGE +-- LLAgent.setCamera{ [, camera_pos] -- vector3 +-- [, focus_pos] -- vector3 +-- [, focus_offset] -- vector3 -- {1,0,0} -- {-10,-10,-10} to {10,10,10} +-- [, distance] -- float (meters) -- 3 -- 0.5 to 50 +-- [, focus_threshold] -- float (meters) -- 1 -- 0 to 4 +-- [, camera_threshold] -- float (meters) -- 1 -- 0 to 4 +-- [, focus_lag] -- float (seconds) -- 0.1 -- 0 to 3 +-- [, camera_lag] -- float (seconds) -- 0.1 -- 0 to 3 +-- [, camera_pitch] -- float (degrees) -- 0 -- -45 to 80 +-- [, behindness_angle] -- float (degrees) -- 10 -- 0 to 180 +-- [, behindness_lag] -- float (seconds) -- 0 -- 0 to 3 +-- [, camera_locked] -- bool -- false +-- [, focus_locked]} -- bool -- false function LLAgent.setCamera(...) - local args = mapargs('camera_pos,focus_pos,focus_offset,camera_locked,focus_locked', ...) + local args = mapargs('camera_pos,focus_pos,focus_offset,focus_lag,camera_lag,' .. + 'distance,focus_threshold,camera_threshold,camera_pitch,' .. + 'camera_locked,focus_locked,behindness_angle,behindness_lag', ...) args.op = 'setCameraParams' leap.send('LLAgent', args) end -- cgit v1.2.3 From ed388f61bad7f6873ee3a2e4d673c9b991b4ef88 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Mon, 29 Jul 2024 16:51:37 +0300 Subject: Update expired cert in integration test (#2140) see fe8c976 for more info Co-authored-by: Andrey Lihatskiy --- indra/newview/tests/llsechandler_basic_test.cpp | 670 ++++++++++++------------ 1 file changed, 345 insertions(+), 325 deletions(-) (limited to 'indra') diff --git a/indra/newview/tests/llsechandler_basic_test.cpp b/indra/newview/tests/llsechandler_basic_test.cpp index bfe32406cb..cf37a4713c 100644 --- a/indra/newview/tests/llsechandler_basic_test.cpp +++ b/indra/newview/tests/llsechandler_basic_test.cpp @@ -232,381 +232,402 @@ namespace tut "Certificate:\n" " Data:\n" " Version: 3 (0x2)\n" - " Serial Number:\n" - " 82:2f:8f:eb:8d:06:24:b0\n" + " Serial Number: ef:54:d8:f7:da:18:e8:19\n" " Signature Algorithm: sha256WithRSAEncryption\n" " Issuer: C=US, ST=California, L=San Francisco, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Root CA/emailAddress=noreply@lindenlab.com\n" " Validity\n" - " Not Before: May 22 22:19:45 2018 GMT\n" - " Not After : May 17 22:19:45 2038 GMT\n" + " Not Before: Jul 23 11:46:26 2024 GMT\n" + " Not After : Jul 21 11:46:26 2034 GMT\n" " Subject: C=US, ST=California, L=San Francisco, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Root CA/emailAddress=noreply@lindenlab.com\n" " Subject Public Key Info:\n" " Public Key Algorithm: rsaEncryption\n" " Public-Key: (4096 bit)\n" " Modulus:\n" - " 00:bd:e0:79:dd:3b:a6:ac:87:d0:39:f0:58:c7:a4:\n" - " 42:42:f6:5f:93:b0:36:04:b5:e2:d5:f7:2a:c0:6c:\n" - " a0:13:d2:1e:02:81:57:02:50:4c:57:b7:ef:27:9e:\n" - " f6:f1:f1:30:30:72:1e:57:34:e5:3f:82:3c:21:c4:\n" - " 66:d2:73:63:6c:91:e6:dd:49:9e:9c:b1:34:6a:81:\n" - " 45:a1:6e:c4:50:28:f2:d8:e3:fe:80:2f:83:aa:28:\n" - " 91:b4:8c:57:c9:f1:16:d9:0c:87:3c:25:80:a0:81:\n" - " 8d:71:f2:96:e2:16:f1:97:c4:b0:d8:53:bb:13:6c:\n" - " 73:54:2f:29:94:85:cf:86:6e:75:71:ad:39:e3:fc:\n" - " 39:12:53:93:1c:ce:39:e0:33:da:49:b7:3d:af:b0:\n" - " 37:ce:77:09:03:27:32:70:c0:9c:7f:9c:89:ce:90:\n" - " 45:b0:7d:94:8b:ff:13:27:ba:88:7f:ae:c4:aa:73:\n" - " d5:47:b8:87:69:89:80:0c:c1:22:18:78:c2:0d:47:\n" - " d9:10:ff:80:79:0d:46:71:ec:d9:ba:c9:f3:77:fd:\n" - " 92:6d:1f:0f:d9:54:18:6d:f6:72:24:5c:5c:3d:43:\n" - " 49:35:3e:1c:28:de:7e:44:dc:29:c3:9f:62:04:46:\n" - " aa:c4:e6:69:6a:15:f8:e3:74:1c:14:e9:f4:97:7c:\n" - " 30:6c:d4:28:fc:2a:0e:1d:6d:39:2e:1d:f9:17:43:\n" - " 35:5d:23:e7:ba:e3:a8:e9:97:6b:3c:3e:23:ef:d8:\n" - " bc:fb:7a:57:37:39:93:59:03:fc:78:ca:b1:31:ef:\n" - " 26:19:ed:56:e1:63:c3:ad:99:80:5b:47:b5:03:35:\n" - " 5f:fe:6a:a6:21:63:ec:50:fb:4e:c9:f9:ae:a5:66:\n" - " d0:55:33:8d:e6:c5:50:5a:c6:8f:5c:34:45:a7:72:\n" - " da:50:f6:66:4c:19:f5:d1:e4:fb:11:8b:a1:b5:4e:\n" - " 09:43:81:3d:39:28:86:3b:fe:07:28:97:02:b5:3a:\n" - " 07:5f:4a:20:80:1a:7d:a4:8c:f7:6c:f6:c5:9b:f6:\n" - " 61:e5:c7:b0:c3:d5:58:38:7b:bb:47:1e:34:d6:16:\n" - " 55:c5:d2:6c:b0:93:77:b1:90:69:06:b1:53:cb:1b:\n" - " 84:71:cf:b8:87:1b:1e:44:35:b4:2b:bb:04:59:58:\n" - " 0b:e8:93:d8:ae:21:9b:b1:1c:89:30:ae:11:80:77:\n" - " cc:16:f3:d6:35:ed:a1:b3:70:b3:4f:cd:a1:56:99:\n" - " ee:0e:c0:00:a4:09:70:c3:5b:0b:be:a1:07:18:dd:\n" - " c6:f4:6d:8b:58:bc:f9:bb:4b:01:2c:f6:cc:2c:9b:\n" - " 87:0e:b1:4f:9c:10:be:fc:45:e2:a4:ec:7e:fc:ff:\n" - " 45:b8:53\n" + " 00:c6:cc:07:f4:0b:17:06:4d:a6:30:b4:c7:02:6b:\n" + " 9d:a4:47:a6:09:0e:60:1a:32:d4:6b:42:88:ee:c5:\n" + " b9:e9:fb:b5:0b:60:dc:a2:45:92:a5:bb:88:12:fc:\n" + " 42:1a:80:32:79:16:62:7a:97:af:84:28:53:3c:c1:\n" + " f2:68:c0:4e:45:e4:0a:63:f9:34:1d:a2:8b:cc:70:\n" + " df:c6:65:c0:ba:31:32:d2:9d:0c:c8:ce:dc:11:12:\n" + " a4:11:fa:d3:c8:56:e2:31:8a:e3:fb:91:40:da:25:\n" + " 55:d1:f2:75:9b:4d:fa:b8:1f:b5:6d:9b:e1:fe:5d:\n" + " e8:c4:02:79:14:ef:7d:5a:b3:3a:1e:b6:d0:60:2c:\n" + " 90:dc:22:e2:c5:ae:85:1f:b4:9d:7a:20:f8:af:63:\n" + " 56:25:1a:64:f3:9c:3f:9a:cf:68:08:0a:37:db:d0:\n" + " a3:65:26:db:80:82:ff:e0:1b:51:c8:ee:f6:ad:c2:\n" + " b4:f2:ab:d2:e8:85:86:77:28:d0:63:4a:71:78:41:\n" + " e3:8c:7f:71:51:31:af:24:3f:fa:8d:d0:d8:0b:e2:\n" + " 7e:79:33:8a:bb:d2:00:9e:2e:c8:cd:d5:50:92:b8:\n" + " 5c:5a:0b:99:ef:05:39:67:da:be:70:36:51:37:37:\n" + " 20:6f:84:ab:29:11:00:7b:38:32:ba:0b:bc:34:a6:\n" + " b5:c6:a7:f0:c0:25:2d:38:0b:72:40:ab:cf:e6:ff:\n" + " 97:75:ff:e2:a9:3c:2a:57:ce:e4:52:20:8c:de:fe:\n" + " 68:ce:54:85:37:ba:b3:7f:2e:53:58:ea:9b:ac:79:\n" + " 6b:16:65:b8:11:88:5a:46:eb:9e:9e:80:3c:89:91:\n" + " 35:e0:c5:33:45:c8:86:4d:25:51:39:b1:72:97:2b:\n" + " b3:c8:c9:e8:11:cd:32:41:c8:c1:56:22:7e:33:81:\n" + " 85:61:ab:da:9e:6e:5f:24:1c:0f:9b:fa:da:9d:86:\n" + " 1a:66:f6:32:2a:10:80:ea:72:7a:4a:ef:c0:f2:7c:\n" + " 43:02:e6:70:19:6a:e1:02:0a:00:80:51:1c:a3:03:\n" + " 8b:6d:89:9f:91:37:90:d6:d8:9c:73:77:06:9e:bc:\n" + " 95:89:66:ee:43:40:a3:ee:43:a3:f6:2d:43:dd:7b:\n" + " f0:2f:0b:12:37:49:b7:81:5a:e2:54:6d:71:88:ff:\n" + " fe:7e:41:25:35:4c:b4:b9:62:65:dd:9f:1f:7a:06:\n" + " 6e:2b:20:58:78:da:08:66:a8:f1:89:de:8f:7f:5c:\n" + " 5e:c2:72:33:7f:b6:8e:41:4c:26:f6:4c:d4:0e:11:\n" + " 44:da:c7:14:f7:8b:79:4e:53:29:87:15:b1:12:e9:\n" + " 19:2b:54:33:d6:2e:7f:bd:42:20:be:fc:d7:9c:b4:\n" + " 7a:0a:db\n" " Exponent: 65537 (0x10001)\n" " X509v3 extensions:\n" - " X509v3 Subject Key Identifier: \n" - " 8A:22:C6:9C:2E:11:F3:40:0C:CE:82:0C:22:59:FF:F8:7F:D0:B9:13\n" - " X509v3 Authority Key Identifier: \n" - " keyid:8A:22:C6:9C:2E:11:F3:40:0C:CE:82:0C:22:59:FF:F8:7F:D0:B9:13\n" + " X509v3 Subject Key Identifier:\n" + " 4D:7D:AE:0D:A5:5E:22:5A:6A:8F:19:61:54:B3:58:CB:7B:C0:BD:DA\n" + " X509v3 Authority Key Identifier:\n" + " keyid:4D:7D:AE:0D:A5:5E:22:5A:6A:8F:19:61:54:B3:58:CB:7B:C0:BD:DA\n" "\n" - " X509v3 Basic Constraints: critical\n" + " X509v3 Basic Constraints:\n" " CA:TRUE\n" - " X509v3 Key Usage: critical\n" - " Digital Signature, Certificate Sign, CRL Sign\n" " Signature Algorithm: sha256WithRSAEncryption\n" - " b3:cb:33:eb:0e:02:64:f4:55:9a:3d:03:9a:cf:6a:4c:18:43:\n" - " f7:42:cb:65:dc:61:52:e5:9f:2f:42:97:3c:93:16:22:d4:af:\n" - " ae:b2:0f:c3:9b:ef:e0:cc:ee:b6:b1:69:a3:d8:da:26:c3:ad:\n" - " 3b:c5:64:dc:9f:d4:c2:53:4b:91:6d:c4:92:09:0b:ac:f0:99:\n" - " be:6f:b9:3c:03:4a:6d:9f:01:5d:ec:5a:9a:f3:a7:e5:3b:2c:\n" - " 99:57:7d:7e:25:15:68:20:12:30:96:16:86:f5:db:74:90:60:\n" - " fe:8b:df:99:f6:f7:62:49:9f:bc:8d:45:23:0a:c8:73:b8:79:\n" - " 80:3c:b9:e5:72:85:4b:b3:81:66:74:a2:72:92:4c:44:fd:7b:\n" - " 46:2e:21:a2:a9:81:a2:f3:26:4d:e3:89:7d:78:b0:c6:6f:b5:\n" - " 87:cb:ee:25:ed:27:1f:75:13:fa:6d:e9:37:73:ad:07:bb:af:\n" - " d3:6c:87:ea:02:01:70:bd:53:aa:ce:39:2c:d4:66:39:33:aa:\n" - " d1:9c:ee:67:e3:a9:45:d2:7b:2e:54:09:af:70:5f:3f:5a:67:\n" - " 2e:6c:72:ef:e0:9d:92:28:4a:df:ba:0b:b7:23:ca:5b:04:11:\n" - " 45:d1:51:e9:ea:c9:ec:54:fa:34:46:ae:fc:dc:6c:f8:1e:2c:\n" - " 9e:f4:71:51:8d:b5:a1:26:9a:13:30:be:1e:41:25:59:58:05:\n" - " 2c:64:c8:f9:5e:38:ae:dc:93:b0:8a:d6:38:74:02:cb:ce:ce:\n" - " 95:31:76:f6:7c:bf:a4:a1:8e:27:fd:ca:74:82:d1:e1:4d:b6:\n" - " 48:51:fa:c5:17:59:22:a3:84:be:82:c8:83:ec:61:a0:f4:ee:\n" - " 2c:e3:a3:ea:e5:51:c9:d3:4f:db:85:bd:ba:7a:52:14:b6:03:\n" - " ed:43:17:d8:d7:1c:22:5e:c9:56:d9:d6:81:96:11:e3:5e:01:\n" - " 40:91:30:09:da:a3:5f:d3:27:60:e5:9d:6c:da:d0:f0:39:01:\n" - " 23:4a:a6:15:7a:4a:82:eb:ec:72:4a:1d:36:dc:6f:83:c4:85:\n" - " 84:b5:8d:cd:09:e5:12:63:f3:21:56:c8:64:6b:db:b8:cf:d4:\n" - " df:ca:a8:24:8e:df:8d:63:a5:96:84:bf:ff:8b:7e:46:7a:f0:\n" - " c7:73:7c:70:8a:f5:17:d0:ac:c8:89:1e:d7:89:42:0f:4d:66:\n" - " c4:d8:bb:36:a8:ae:ca:e1:cf:e2:88:f6:cf:b0:44:4a:5f:81:\n" - " 50:4b:d6:28:81:cd:6c:f0:ec:e6:09:08:f2:59:91:a2:69:ac:\n" - " c7:81:fa:ab:61:3e:db:6f:f6:7f:db:1a:9e:b9:5d:cc:cc:33:\n" - " fa:95:c6:f7:8d:4b:30:f3\n" + " 5b:40:71:96:c8:d1:57:3f:fc:f2:3c:75:fb:c9:a6:a7:63:8a:\n" + " 22:23:96:0f:40:77:77:e2:7f:76:fc:5f:7b:1c:bd:ea:ca:f0:\n" + " be:1a:fd:59:e6:0e:00:d1:78:44:01:28:f4:01:68:67:78:cf:\n" + " 78:43:36:ac:b2:5c:13:0e:2a:94:59:88:9e:64:46:42:0a:9b:\n" + " be:7d:2d:10:11:fe:8b:64:01:fb:00:c5:2e:47:63:c0:93:3a:\n" + " 4a:f8:6c:fc:a9:16:58:ab:bc:7b:6b:20:31:9d:d7:d8:84:01:\n" + " cc:ce:52:7f:a1:18:2f:5c:c9:59:58:9a:98:b9:ef:54:d7:a0:\n" + " 56:79:28:ba:ad:f5:e5:fd:7e:d8:d6:be:dd:25:76:6f:fa:8a:\n" + " 07:f6:8e:0f:83:43:19:ee:96:c4:c9:54:df:19:5a:4c:ae:25:\n" + " 57:a2:5d:d5:e8:0a:66:d8:19:e9:c4:44:ba:6a:3b:b3:86:ae:\n" + " 44:c0:7c:6e:e5:a0:6c:45:bb:7f:34:94:e9:d3:d4:f4:04:0b:\n" + " eb:fc:9a:fa:67:d4:e5:83:5e:08:09:9c:70:a9:d3:0d:8a:08:\n" + " ed:3c:04:33:4f:ac:02:d9:5c:99:62:12:fc:0e:8d:55:8a:ce:\n" + " ca:28:5a:1a:9e:c9:59:8e:f0:f5:19:c7:30:1e:59:1f:3c:77:\n" + " 6d:fc:a2:31:ec:bf:83:fd:14:26:91:68:88:05:4c:87:82:e0:\n" + " 33:f4:ee:d8:56:97:23:3a:00:9b:e7:a2:10:c2:83:28:c6:c0:\n" + " c1:92:49:95:c1:d3:e1:43:e8:8f:0c:d0:ae:e3:50:17:1a:8d:\n" + " 0f:4a:60:71:76:8e:9e:fb:15:76:cd:cd:69:2c:59:24:69:d2:\n" + " 0f:f2:d5:0e:96:95:2b:2e:d7:81:ed:b3:7b:6f:ce:60:32:b5:\n" + " f0:f6:74:ea:27:3a:ee:2c:96:7b:e0:06:6c:33:25:c4:60:da:\n" + " 76:de:c4:a1:22:b6:b1:63:57:10:3c:62:60:98:47:39:9e:38:\n" + " ce:c7:ef:75:75:19:d3:26:2a:cf:46:e3:b0:72:38:49:ee:c3:\n" + " 4e:52:97:e5:e5:b8:bc:b1:45:56:98:54:0a:63:c8:87:ff:a0:\n" + " cb:28:12:5c:8f:a2:6e:a7:f9:50:98:2d:a5:26:08:df:16:29:\n" + " 19:63:7f:6c:b4:41:20:f7:5d:ef:6a:90:fd:1a:08:1c:c2:4c:\n" + " 3e:77:ea:e0:df:c0:dd:aa:a2:36:e7:e8:be:98:39:0a:68:59:\n" + " 8e:a0:71:2f:7c:92:ab:e0:c4:c1:c2:eb:89:b6:34:ce:44:ab:\n" + " f9:f6:a4:c8:7b:ad:a8:bc:c9:04:7c:d5:4c:a4:d2:8b:54:23:\n" + " 89:68:86:4e:07:36:d9:bc\n" "-----BEGIN CERTIFICATE-----\n" - "MIIGXDCCBESgAwIBAgIJAIIvj+uNBiSwMA0GCSqGSIb3DQEBCwUAMIG6MQswCQYD\n" + "MIIGSTCCBDGgAwIBAgIJAO9U2PfaGOgZMA0GCSqGSIb3DQEBCwUAMIG6MQswCQYD\n" "VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j\n" "aXNjbzETMBEGA1UECgwKTGluZGVuIExhYjEgMB4GA1UECwwXU2Vjb25kIExpZmUg\n" "RW5naW5lZXJpbmcxITAfBgNVBAMMGEludGVncmF0aW9uIFRlc3QgUm9vdCBDQTEk\n" - "MCIGCSqGSIb3DQEJARYVbm9yZXBseUBsaW5kZW5sYWIuY29tMB4XDTE4MDUyMjIy\n" - "MTk0NVoXDTM4MDUxNzIyMTk0NVowgboxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApD\n" + "MCIGCSqGSIb3DQEJARYVbm9yZXBseUBsaW5kZW5sYWIuY29tMB4XDTI0MDcyMzEx\n" + "NDYyNloXDTM0MDcyMTExNDYyNlowgboxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApD\n" "YWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMwEQYDVQQKDApMaW5k\n" "ZW4gTGFiMSAwHgYDVQQLDBdTZWNvbmQgTGlmZSBFbmdpbmVlcmluZzEhMB8GA1UE\n" "AwwYSW50ZWdyYXRpb24gVGVzdCBSb290IENBMSQwIgYJKoZIhvcNAQkBFhVub3Jl\n" "cGx5QGxpbmRlbmxhYi5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC\n" - "AQC94HndO6ash9A58FjHpEJC9l+TsDYEteLV9yrAbKAT0h4CgVcCUExXt+8nnvbx\n" - "8TAwch5XNOU/gjwhxGbSc2NskebdSZ6csTRqgUWhbsRQKPLY4/6AL4OqKJG0jFfJ\n" - "8RbZDIc8JYCggY1x8pbiFvGXxLDYU7sTbHNULymUhc+GbnVxrTnj/DkSU5Mczjng\n" - "M9pJtz2vsDfOdwkDJzJwwJx/nInOkEWwfZSL/xMnuoh/rsSqc9VHuIdpiYAMwSIY\n" - "eMINR9kQ/4B5DUZx7Nm6yfN3/ZJtHw/ZVBht9nIkXFw9Q0k1Phwo3n5E3CnDn2IE\n" - "RqrE5mlqFfjjdBwU6fSXfDBs1Cj8Kg4dbTkuHfkXQzVdI+e646jpl2s8PiPv2Lz7\n" - "elc3OZNZA/x4yrEx7yYZ7VbhY8OtmYBbR7UDNV/+aqYhY+xQ+07J+a6lZtBVM43m\n" - "xVBaxo9cNEWnctpQ9mZMGfXR5PsRi6G1TglDgT05KIY7/gcolwK1OgdfSiCAGn2k\n" - "jPds9sWb9mHlx7DD1Vg4e7tHHjTWFlXF0mywk3exkGkGsVPLG4Rxz7iHGx5ENbQr\n" - "uwRZWAvok9iuIZuxHIkwrhGAd8wW89Y17aGzcLNPzaFWme4OwACkCXDDWwu+oQcY\n" - "3cb0bYtYvPm7SwEs9swsm4cOsU+cEL78ReKk7H78/0W4UwIDAQABo2MwYTAdBgNV\n" - "HQ4EFgQUiiLGnC4R80AMzoIMIln/+H/QuRMwHwYDVR0jBBgwFoAUiiLGnC4R80AM\n" - "zoIMIln/+H/QuRMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJ\n" - "KoZIhvcNAQELBQADggIBALPLM+sOAmT0VZo9A5rPakwYQ/dCy2XcYVLlny9ClzyT\n" - "FiLUr66yD8Ob7+DM7raxaaPY2ibDrTvFZNyf1MJTS5FtxJIJC6zwmb5vuTwDSm2f\n" - "AV3sWprzp+U7LJlXfX4lFWggEjCWFob123SQYP6L35n292JJn7yNRSMKyHO4eYA8\n" - "ueVyhUuzgWZ0onKSTET9e0YuIaKpgaLzJk3jiX14sMZvtYfL7iXtJx91E/pt6Tdz\n" - "rQe7r9Nsh+oCAXC9U6rOOSzUZjkzqtGc7mfjqUXSey5UCa9wXz9aZy5scu/gnZIo\n" - "St+6C7cjylsEEUXRUenqyexU+jRGrvzcbPgeLJ70cVGNtaEmmhMwvh5BJVlYBSxk\n" - "yPleOK7ck7CK1jh0AsvOzpUxdvZ8v6Shjif9ynSC0eFNtkhR+sUXWSKjhL6CyIPs\n" - "YaD07izjo+rlUcnTT9uFvbp6UhS2A+1DF9jXHCJeyVbZ1oGWEeNeAUCRMAnao1/T\n" - "J2DlnWza0PA5ASNKphV6SoLr7HJKHTbcb4PEhYS1jc0J5RJj8yFWyGRr27jP1N/K\n" - "qCSO341jpZaEv/+LfkZ68MdzfHCK9RfQrMiJHteJQg9NZsTYuzaorsrhz+KI9s+w\n" - "REpfgVBL1iiBzWzw7OYJCPJZkaJprMeB+qthPttv9n/bGp65XczMM/qVxveNSzDz\n" + "AQDGzAf0CxcGTaYwtMcCa52kR6YJDmAaMtRrQojuxbnp+7ULYNyiRZKlu4gS/EIa\n" + "gDJ5FmJ6l6+EKFM8wfJowE5F5Apj+TQdoovMcN/GZcC6MTLSnQzIztwREqQR+tPI\n" + "VuIxiuP7kUDaJVXR8nWbTfq4H7Vtm+H+XejEAnkU731aszoettBgLJDcIuLFroUf\n" + "tJ16IPivY1YlGmTznD+az2gICjfb0KNlJtuAgv/gG1HI7vatwrTyq9LohYZ3KNBj\n" + "SnF4QeOMf3FRMa8kP/qN0NgL4n55M4q70gCeLsjN1VCSuFxaC5nvBTln2r5wNlE3\n" + "NyBvhKspEQB7ODK6C7w0prXGp/DAJS04C3JAq8/m/5d1/+KpPCpXzuRSIIze/mjO\n" + "VIU3urN/LlNY6puseWsWZbgRiFpG656egDyJkTXgxTNFyIZNJVE5sXKXK7PIyegR\n" + "zTJByMFWIn4zgYVhq9qebl8kHA+b+tqdhhpm9jIqEIDqcnpK78DyfEMC5nAZauEC\n" + "CgCAURyjA4ttiZ+RN5DW2JxzdwaevJWJZu5DQKPuQ6P2LUPde/AvCxI3SbeBWuJU\n" + "bXGI//5+QSU1TLS5YmXdnx96Bm4rIFh42ghmqPGJ3o9/XF7CcjN/to5BTCb2TNQO\n" + "EUTaxxT3i3lOUymHFbES6RkrVDPWLn+9QiC+/NectHoK2wIDAQABo1AwTjAdBgNV\n" + "HQ4EFgQUTX2uDaVeIlpqjxlhVLNYy3vAvdowHwYDVR0jBBgwFoAUTX2uDaVeIlpq\n" + "jxlhVLNYy3vAvdowDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAW0Bx\n" + "lsjRVz/88jx1+8mmp2OKIiOWD0B3d+J/dvxfexy96srwvhr9WeYOANF4RAEo9AFo\n" + "Z3jPeEM2rLJcEw4qlFmInmRGQgqbvn0tEBH+i2QB+wDFLkdjwJM6Svhs/KkWWKu8\n" + "e2sgMZ3X2IQBzM5Sf6EYL1zJWViamLnvVNegVnkouq315f1+2Na+3SV2b/qKB/aO\n" + "D4NDGe6WxMlU3xlaTK4lV6Jd1egKZtgZ6cREumo7s4auRMB8buWgbEW7fzSU6dPU\n" + "9AQL6/ya+mfU5YNeCAmccKnTDYoI7TwEM0+sAtlcmWIS/A6NVYrOyihaGp7JWY7w\n" + "9RnHMB5ZHzx3bfyiMey/g/0UJpFoiAVMh4LgM/Tu2FaXIzoAm+eiEMKDKMbAwZJJ\n" + "lcHT4UPojwzQruNQFxqND0pgcXaOnvsVds3NaSxZJGnSD/LVDpaVKy7Xge2ze2/O\n" + "YDK18PZ06ic67iyWe+AGbDMlxGDadt7EoSK2sWNXEDxiYJhHOZ44zsfvdXUZ0yYq\n" + "z0bjsHI4Se7DTlKX5eW4vLFFVphUCmPIh/+gyygSXI+ibqf5UJgtpSYI3xYpGWN/\n" + "bLRBIPdd72qQ/RoIHMJMPnfq4N/A3aqiNufovpg5CmhZjqBxL3ySq+DEwcLribY0\n" + "zkSr+fakyHutqLzJBHzVTKTSi1QjiWiGTgc22bw=\n" "-----END CERTIFICATE-----\n" - ); + ); + const std::string mPemIntermediateCert( "Certificate:\n" " Data:\n" " Version: 3 (0x2)\n" - " Serial Number: 4096 (0x1000)\n" + " Serial Number: 85:bb:4b:66:26:db:9a:c6\n" " Signature Algorithm: sha256WithRSAEncryption\n" " Issuer: C=US, ST=California, L=San Francisco, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Root CA/emailAddress=noreply@lindenlab.com\n" " Validity\n" - " Not Before: May 22 22:39:08 2018 GMT\n" - " Not After : May 19 22:39:08 2028 GMT\n" - " Subject: C=US, ST=California, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Intermediate CA/emailAddress=noreply@lindenlab.com\n" + " Not Before: Jul 23 11:46:33 2024 GMT\n" + " Not After : Jul 21 11:46:33 2034 GMT\n" + " Subject: C=US, ST=California, L=San Francisco, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Intermediate CA/emailAddress=noreply@lindenlab.com\n" " Subject Public Key Info:\n" " Public Key Algorithm: rsaEncryption\n" " Public-Key: (4096 bit)\n" " Modulus:\n" - " 00:ce:a3:70:e2:c4:fb:4b:97:90:a1:30:bb:c1:1b:\n" - " 13:b9:aa:7e:46:17:a3:26:8d:69:3f:5e:73:95:e8:\n" - " 6a:b1:0a:b4:8f:50:65:e3:c6:5c:39:24:34:df:0b:\n" - " b7:cc:ce:62:0c:36:5a:12:2c:fe:35:4c:e9:1c:ac:\n" - " 80:5e:24:99:d7:aa:bd:be:48:c0:62:64:77:36:88:\n" - " 66:ce:f4:a8:dd:d2:76:24:62:90:55:41:fc:1d:13:\n" - " 4e:a7:4e:57:bc:a8:a4:59:4b:2c:5a:1c:d8:cc:16:\n" - " de:e8:88:30:c9:95:df:2f:a6:14:28:0f:eb:34:46:\n" - " 12:58:ba:da:0e:e6:de:9c:15:f6:f4:e3:9f:74:aa:\n" - " 70:89:79:8b:e9:5a:7b:18:54:15:94:3a:23:0a:65:\n" - " 78:05:d9:33:90:2a:ce:15:18:0d:52:fc:5c:31:65:\n" - " 20:d0:12:37:8c:11:80:ba:d4:b0:82:73:00:4b:49:\n" - " be:cb:d6:bc:e7:cd:61:f3:00:98:99:74:5a:37:81:\n" - " 49:96:7e:14:01:1b:86:d2:d0:06:94:40:63:63:46:\n" - " 11:fc:33:5c:bd:3a:5e:d4:e5:44:47:64:50:bd:a6:\n" - " 97:55:70:64:9b:26:cc:de:20:82:90:6a:83:41:9c:\n" - " 6f:71:47:14:be:cb:68:7c:85:be:ef:2e:76:12:19:\n" - " d3:c9:87:32:b4:ac:60:20:16:28:2d:af:bc:e8:01:\n" - " c6:7f:fb:d8:11:d5:f4:b7:14:bd:27:08:5b:72:be:\n" - " 09:e0:91:c8:9c:7b:b4:b3:12:ef:32:36:be:b1:b9:\n" - " a2:b7:e3:69:47:30:76:ba:9c:9b:19:99:4d:53:dd:\n" - " 5c:e8:2c:f1:b2:64:69:cf:15:bd:f8:bb:58:95:73:\n" - " 58:38:95:b4:7a:cf:84:29:a6:c2:db:f0:bd:ef:97:\n" - " 26:d4:99:ac:d7:c7:be:b0:0d:11:f4:26:86:2d:77:\n" - " 42:52:25:d7:56:c7:e3:97:b1:36:5c:97:71:d0:9b:\n" - " f5:b5:50:8d:f9:ff:fb:10:77:3c:b5:53:6d:a1:43:\n" - " 35:a9:03:32:05:ab:d7:f5:d1:19:bd:5f:92:a3:00:\n" - " 2a:79:37:a4:76:4f:e9:32:0d:e4:86:bb:ea:c3:1a:\n" - " c5:33:e8:16:d4:a5:d8:e0:e8:bb:c2:f0:22:15:e2:\n" - " d9:8c:ae:ac:7d:2b:bf:eb:a3:4c:3b:29:1d:94:ac:\n" - " a3:bb:6d:ba:6d:03:91:03:cf:46:12:c4:66:21:c5:\n" - " c6:67:d8:11:19:79:01:0e:6e:84:1c:76:6f:11:3d:\n" - " eb:94:89:c5:6a:26:1f:cd:e0:11:8b:51:ee:99:35:\n" - " 69:e5:7f:0b:77:2a:94:e4:4b:64:b9:83:04:30:05:\n" - " e4:a2:e3\n" + " 00:be:f7:d2:cb:e4:5c:46:7b:e2:11:22:89:72:da:\n" + " 77:72:ec:05:87:19:f7:77:07:fd:67:d7:af:13:d5:\n" + " 76:12:92:dd:69:4d:22:47:b0:3d:94:8a:6a:95:85:\n" + " 34:b8:78:c3:9d:63:32:b1:4b:0a:b6:0e:05:7b:ab:\n" + " 06:23:fc:0d:21:b5:fc:c6:6a:5a:36:be:6e:fc:c7:\n" + " 47:97:a3:18:2e:33:cd:0e:8a:75:2b:b7:29:e9:68:\n" + " 4a:90:53:45:db:73:ff:b3:e5:c1:d4:6b:dd:3a:b1:\n" + " ef:53:9f:23:e9:c6:87:ce:67:b9:fb:a4:d5:76:21:\n" + " 03:cb:c5:72:6b:c5:a6:07:55:fb:47:90:e8:92:38:\n" + " 73:14:11:8e:ff:21:b9:35:64:5a:61:c7:fc:1f:e4:\n" + " 4d:47:e5:03:cc:0b:c3:69:66:71:84:0c:18:2f:61:\n" + " 7f:34:dd:f2:91:e3:b7:9d:a8:b8:db:3f:6e:6f:96:\n" + " fa:34:06:82:04:c8:18:cc:de:8b:7f:26:b5:48:53:\n" + " fb:fb:15:7b:0e:38:60:fe:da:21:98:8d:73:07:b2:\n" + " 6b:fd:ad:21:59:e7:84:66:e1:04:16:1c:be:13:34:\n" + " 28:43:2c:09:3d:e4:77:2a:a4:ad:6d:f9:26:04:f7:\n" + " 43:73:9b:d9:ea:1a:43:6a:b4:db:88:f8:f9:bd:34:\n" + " f8:a6:e8:7a:ab:b4:b2:e1:29:47:a6:ba:b8:65:9c:\n" + " c6:b3:af:13:43:38:ef:2a:05:77:9f:8f:f0:0c:56:\n" + " 21:c2:92:d2:2c:c3:32:50:d1:62:ae:51:fc:99:e6:\n" + " b8:38:f8:83:1d:8d:40:11:e0:1d:51:5d:3f:fa:55:\n" + " 61:b6:18:09:1e:71:af:95:64:9c:ea:c6:11:64:f0:\n" + " a8:02:7d:bb:c8:54:2e:57:48:32:7c:51:66:0d:d6:\n" + " 3e:0e:ed:5e:30:a8:a6:47:03:64:5c:89:21:45:90:\n" + " e1:4c:91:bc:bd:81:6e:73:a9:14:27:e6:0d:6d:38:\n" + " dc:50:9d:b2:56:66:60:6c:66:b9:5d:bb:8c:96:2d:\n" + " 89:5e:0d:2b:ed:b8:03:31:ce:0a:ff:82:03:f5:b2:\n" + " 3b:e5:27:de:61:d8:8f:bf:a2:6a:64:b0:4a:87:23:\n" + " 40:28:a3:f1:ec:96:50:cd:83:50:2d:78:71:92:f2:\n" + " 88:75:b0:9d:cd:0b:e4:62:a6:a5:63:11:fc:b4:ba:\n" + " 9f:c6:67:40:2c:ad:a4:ef:94:f0:f9:a0:ba:e1:52:\n" + " 2e:27:d9:6b:1d:82:23:ed:3c:0b:0b:d2:bc:14:be:\n" + " 6d:b1:69:ad:3e:25:3a:66:d2:d1:af:9f:88:45:25:\n" + " 6b:6e:be:1f:a0:e7:b2:9f:6d:24:94:0d:f4:c2:75:\n" + " f9:1f:5d\n" " Exponent: 65537 (0x10001)\n" " X509v3 extensions:\n" - " X509v3 Subject Key Identifier: \n" - " 83:21:DE:EC:C0:79:03:6D:1E:83:F3:E5:97:29:D5:5A:C0:96:40:FA\n" - " X509v3 Authority Key Identifier: \n" - " keyid:8A:22:C6:9C:2E:11:F3:40:0C:CE:82:0C:22:59:FF:F8:7F:D0:B9:13\n" - "\n" - " X509v3 Basic Constraints: critical\n" + " X509v3 Basic Constraints:\n" " CA:TRUE, pathlen:0\n" - " X509v3 Key Usage: critical\n" + " X509v3 Key Usage:\n" " Digital Signature, Certificate Sign, CRL Sign\n" + " X509v3 Subject Key Identifier:\n" + " 56:98:DC:45:25:11:E2:8C:2B:EA:D6:C6:E2:C8:BE:2C:C8:69:FF:FF\n" + " X509v3 Authority Key Identifier:\n" + " keyid:4D:7D:AE:0D:A5:5E:22:5A:6A:8F:19:61:54:B3:58:CB:7B:C0:BD:DA\n" + " DirName:/C=US/ST=California/L=San Francisco/O=Linden Lab/OU=Second Life Engineering/CN=Integration Test Root CA/emailAddress=noreply@lindenlab.com\n" + " serial:EF:54:D8:F7:DA:18:E8:19\n" " Signature Algorithm: sha256WithRSAEncryption\n" - " a3:6c:85:9a:2e:4e:7e:5d:83:63:0f:f5:4f:a9:7d:ec:0e:6f:\n" - " ae:d7:ba:df:64:e0:46:0e:3d:da:18:15:2c:f3:73:ca:81:b1:\n" - " 10:d9:53:14:21:7d:72:5c:94:88:a5:9d:ad:ab:45:42:c6:64:\n" - " a9:d9:2e:4e:29:47:2c:b1:95:07:b7:62:48:68:1f:68:13:1c:\n" - " d2:a0:fb:5e:38:24:4a:82:0a:87:c9:93:20:43:7e:e9:f9:79:\n" - " ef:03:a2:bd:9e:24:6b:0a:01:5e:4a:36:c5:7d:7a:fe:d6:aa:\n" - " 2f:c2:8c:38:8a:99:3c:b0:6a:e5:60:be:56:d6:eb:60:03:55:\n" - " 24:42:a0:1a:fa:91:24:a3:53:15:75:5d:c8:eb:7c:1e:68:5a:\n" - " 7e:13:34:e3:85:37:1c:76:3f:77:67:1b:ed:1b:52:17:fc:4a:\n" - " a3:e2:74:84:80:2c:69:fc:dd:7d:26:97:c4:2a:69:7d:9c:dc:\n" - " 61:97:70:29:a7:3f:2b:5b:2b:22:51:fd:fe:6a:5d:f9:e7:14:\n" - " 48:b7:2d:c8:33:58:fc:f2:5f:27:f7:26:16:be:be:b5:aa:a2:\n" - " 64:53:3c:69:e8:b5:61:eb:ab:91:a5:b4:09:9b:f6:98:b8:5c:\n" - " 5b:24:2f:93:f5:2b:9c:8c:58:fb:26:3f:67:53:d7:42:64:e8:\n" - " 79:77:73:41:4e:e3:02:39:0b:b6:68:97:8b:84:e8:1d:83:a8:\n" - " 15:f1:06:46:47:80:42:5e:14:e2:61:8a:76:84:d5:d4:71:7f:\n" - " 4e:ff:d9:74:87:ff:32:c5:87:20:0a:d4:59:40:3e:d8:17:ef:\n" - " da:65:e9:0a:51:fe:1e:c3:46:91:d2:ee:e4:23:57:97:87:d4:\n" - " a6:a5:eb:ef:81:6a:d8:8c:d6:1f:8e:b1:18:4c:6b:89:32:55:\n" - " 53:68:26:9e:bb:03:be:2c:e9:8b:ff:97:9c:1c:ac:28:c3:9f:\n" - " 0b:b7:93:23:24:31:63:e4:19:13:f2:bb:08:71:b7:c5:c5:c4:\n" - " 10:ff:dc:fc:33:54:a4:5e:ec:a3:fe:0a:80:ca:9c:bc:95:6f:\n" - " 5f:39:91:3b:61:69:16:94:0f:57:4b:fc:4b:b1:be:72:98:5d:\n" - " 10:f9:08:a7:d6:e0:e8:3d:5d:54:7d:fa:4b:6a:dd:98:41:ed:\n" - " 84:a1:39:67:5c:6c:7f:0c:b0:e1:98:c1:14:ed:fe:1e:e8:05:\n" - " 8d:7f:6a:24:cb:1b:05:42:0d:7f:13:ba:ca:b5:91:db:a5:f0:\n" - " 40:2b:70:7a:2a:a5:5d:ed:56:0c:f0:c2:72:ee:63:dd:cb:5d:\n" - " 76:f6:08:e6:e6:30:ef:3a:b2:16:34:41:a4:e1:30:14:bc:c7:\n" - " f9:23:3a:1a:70:df:b8:cc\n" + " ae:d0:30:ac:31:49:20:86:0b:34:01:58:08:94:68:cc:38:9c:\n" + " f7:13:5c:46:19:33:ed:54:5e:e4:43:f3:59:33:5c:50:d9:89:\n" + " 8b:ee:75:67:a8:c7:0e:d1:30:c2:4e:a3:2e:a8:64:2d:6a:a8:\n" + " f4:bd:b1:32:dc:bc:46:48:5d:1a:18:d8:e8:0b:8c:fe:7b:51:\n" + " d9:dd:b9:e3:4b:d1:f9:e0:22:46:dd:37:5b:b2:cb:72:8e:9c:\n" + " 4b:da:67:df:fd:ce:86:49:21:31:4e:99:b6:d4:38:0b:14:5d:\n" + " ad:97:ba:8f:e2:08:15:85:73:eb:4a:7d:01:49:af:63:ae:2d:\n" + " e3:9d:0a:d7:11:c2:03:d3:15:21:97:be:3d:d2:ea:ab:cc:93:\n" + " 16:98:64:80:72:eb:c2:78:0a:09:69:c4:2b:5d:df:30:7b:be:\n" + " 9b:02:34:73:62:9f:95:b1:cf:08:e8:9e:57:a8:37:31:cf:2c:\n" + " 8c:18:b1:d5:7a:25:90:d6:b6:76:28:1b:e2:b1:cf:1b:f1:ef:\n" + " dd:2f:d3:07:af:81:e3:5f:fc:5a:e7:3c:a9:37:0d:9c:78:5b:\n" + " 58:dc:89:54:70:a4:5b:ff:9f:64:30:a3:85:12:32:69:a5:02:\n" + " 73:d9:1d:ff:69:1f:d4:97:8f:d0:a8:90:8c:dd:2e:45:a1:b1:\n" + " e3:8a:82:fc:fc:08:41:01:51:92:87:9a:09:7b:35:c3:cc:48:\n" + " 81:39:30:a9:f4:41:3b:06:a3:06:21:cc:4b:bc:1b:76:58:94:\n" + " d1:e4:22:70:7f:20:7e:7a:b4:fa:7f:e8:79:c1:8c:89:9e:e9:\n" + " e3:72:2a:43:72:47:9e:bb:26:ed:64:2c:c8:54:f7:b4:95:c2:\n" + " c4:e9:8b:df:d5:10:a7:ed:a5:7a:94:97:c4:76:45:e3:6c:c0:\n" + " 0e:a6:2a:76:d5:1d:2f:ad:99:32:c6:7b:f6:41:e0:65:37:0f:\n" + " c0:1f:c5:99:4a:75:fd:6c:e0:f1:f0:58:49:2d:81:10:ca:d8:\n" + " eb:2b:c3:9b:a9:d9:a9:f5:6c:6d:26:fd:b8:32:92:58:f4:65:\n" + " 0b:d1:8e:03:1e:d5:6a:95:d4:46:9e:65:dd:e5:85:36:e6:31:\n" + " 77:3a:1a:20:2b:07:b7:f1:9a:4e:8d:54:22:5a:54:1c:72:5c:\n" + " 1f:b4:1a:5b:21:ed:06:5a:9a:e5:3c:01:c9:9b:af:50:61:f2:\n" + " 29:6b:ec:6d:19:bb:2e:02:94:ca:36:71:ef:45:39:f1:a5:25:\n" + " 10:0e:90:bc:a7:b3:5b:ab:af:f1:19:88:6a:09:2f:1f:d0:24:\n" + " a8:62:ed:d9:1a:65:89:65:16:a5:55:de:33:e8:7a:81:66:72:\n" + " 91:17:5e:1d:22:72:f7:b8\n" "-----BEGIN CERTIFICATE-----\n" - "MIIGSDCCBDCgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgboxCzAJBgNVBAYTAlVT\n" - "MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMw\n" - "EQYDVQQKDApMaW5kZW4gTGFiMSAwHgYDVQQLDBdTZWNvbmQgTGlmZSBFbmdpbmVl\n" - "cmluZzEhMB8GA1UEAwwYSW50ZWdyYXRpb24gVGVzdCBSb290IENBMSQwIgYJKoZI\n" - "hvcNAQkBFhVub3JlcGx5QGxpbmRlbmxhYi5jb20wHhcNMTgwNTIyMjIzOTA4WhcN\n" - "MjgwNTE5MjIzOTA4WjCBqjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3Ju\n" - "aWExEzARBgNVBAoMCkxpbmRlbiBMYWIxIDAeBgNVBAsMF1NlY29uZCBMaWZlIEVu\n" - "Z2luZWVyaW5nMSkwJwYDVQQDDCBJbnRlZ3JhdGlvbiBUZXN0IEludGVybWVkaWF0\n" - "ZSBDQTEkMCIGCSqGSIb3DQEJARYVbm9yZXBseUBsaW5kZW5sYWIuY29tMIICIjAN\n" - "BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzqNw4sT7S5eQoTC7wRsTuap+Rhej\n" - "Jo1pP15zlehqsQq0j1Bl48ZcOSQ03wu3zM5iDDZaEiz+NUzpHKyAXiSZ16q9vkjA\n" - "YmR3NohmzvSo3dJ2JGKQVUH8HRNOp05XvKikWUssWhzYzBbe6IgwyZXfL6YUKA/r\n" - "NEYSWLraDubenBX29OOfdKpwiXmL6Vp7GFQVlDojCmV4BdkzkCrOFRgNUvxcMWUg\n" - "0BI3jBGAutSwgnMAS0m+y9a8581h8wCYmXRaN4FJln4UARuG0tAGlEBjY0YR/DNc\n" - "vTpe1OVER2RQvaaXVXBkmybM3iCCkGqDQZxvcUcUvstofIW+7y52EhnTyYcytKxg\n" - "IBYoLa+86AHGf/vYEdX0txS9Jwhbcr4J4JHInHu0sxLvMja+sbmit+NpRzB2upyb\n" - "GZlNU91c6CzxsmRpzxW9+LtYlXNYOJW0es+EKabC2/C975cm1Jms18e+sA0R9CaG\n" - "LXdCUiXXVsfjl7E2XJdx0Jv1tVCN+f/7EHc8tVNtoUM1qQMyBavX9dEZvV+SowAq\n" - "eTekdk/pMg3khrvqwxrFM+gW1KXY4Oi7wvAiFeLZjK6sfSu/66NMOykdlKyju226\n" - "bQORA89GEsRmIcXGZ9gRGXkBDm6EHHZvET3rlInFaiYfzeARi1HumTVp5X8LdyqU\n" - "5EtkuYMEMAXkouMCAwEAAaNmMGQwHQYDVR0OBBYEFIMh3uzAeQNtHoPz5Zcp1VrA\n" - "lkD6MB8GA1UdIwQYMBaAFIoixpwuEfNADM6CDCJZ//h/0LkTMBIGA1UdEwEB/wQI\n" - "MAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQCjbIWa\n" - "Lk5+XYNjD/VPqX3sDm+u17rfZOBGDj3aGBUs83PKgbEQ2VMUIX1yXJSIpZ2tq0VC\n" - "xmSp2S5OKUcssZUHt2JIaB9oExzSoPteOCRKggqHyZMgQ37p+XnvA6K9niRrCgFe\n" - "SjbFfXr+1qovwow4ipk8sGrlYL5W1utgA1UkQqAa+pEko1MVdV3I63weaFp+EzTj\n" - "hTccdj93ZxvtG1IX/Eqj4nSEgCxp/N19JpfEKml9nNxhl3Appz8rWysiUf3+al35\n" - "5xRIty3IM1j88l8n9yYWvr61qqJkUzxp6LVh66uRpbQJm/aYuFxbJC+T9SucjFj7\n" - "Jj9nU9dCZOh5d3NBTuMCOQu2aJeLhOgdg6gV8QZGR4BCXhTiYYp2hNXUcX9O/9l0\n" - "h/8yxYcgCtRZQD7YF+/aZekKUf4ew0aR0u7kI1eXh9SmpevvgWrYjNYfjrEYTGuJ\n" - "MlVTaCaeuwO+LOmL/5ecHKwow58Lt5MjJDFj5BkT8rsIcbfFxcQQ/9z8M1SkXuyj\n" - "/gqAypy8lW9fOZE7YWkWlA9XS/xLsb5ymF0Q+Qin1uDoPV1UffpLat2YQe2EoTln\n" - "XGx/DLDhmMEU7f4e6AWNf2okyxsFQg1/E7rKtZHbpfBAK3B6KqVd7VYM8MJy7mPd\n" - "y1129gjm5jDvOrIWNEGk4TAUvMf5IzoacN+4zA==\n" + "MIIHNjCCBR6gAwIBAgIJAIW7S2Ym25rGMA0GCSqGSIb3DQEBCwUAMIG6MQswCQYD\n" + "VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j\n" + "aXNjbzETMBEGA1UECgwKTGluZGVuIExhYjEgMB4GA1UECwwXU2Vjb25kIExpZmUg\n" + "RW5naW5lZXJpbmcxITAfBgNVBAMMGEludGVncmF0aW9uIFRlc3QgUm9vdCBDQTEk\n" + "MCIGCSqGSIb3DQEJARYVbm9yZXBseUBsaW5kZW5sYWIuY29tMB4XDTI0MDcyMzEx\n" + "NDYzM1oXDTM0MDcyMTExNDYzM1owgcIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApD\n" + "YWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMwEQYDVQQKDApMaW5k\n" + "ZW4gTGFiMSAwHgYDVQQLDBdTZWNvbmQgTGlmZSBFbmdpbmVlcmluZzEpMCcGA1UE\n" + "AwwgSW50ZWdyYXRpb24gVGVzdCBJbnRlcm1lZGlhdGUgQ0ExJDAiBgkqhkiG9w0B\n" + "CQEWFW5vcmVwbHlAbGluZGVubGFiLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP\n" + "ADCCAgoCggIBAL730svkXEZ74hEiiXLad3LsBYcZ93cH/WfXrxPVdhKS3WlNIkew\n" + "PZSKapWFNLh4w51jMrFLCrYOBXurBiP8DSG1/MZqWja+bvzHR5ejGC4zzQ6KdSu3\n" + "KeloSpBTRdtz/7PlwdRr3Tqx71OfI+nGh85nufuk1XYhA8vFcmvFpgdV+0eQ6JI4\n" + "cxQRjv8huTVkWmHH/B/kTUflA8wLw2lmcYQMGC9hfzTd8pHjt52ouNs/bm+W+jQG\n" + "ggTIGMzei38mtUhT+/sVew44YP7aIZiNcweya/2tIVnnhGbhBBYcvhM0KEMsCT3k\n" + "dyqkrW35JgT3Q3Ob2eoaQ2q024j4+b00+Kboequ0suEpR6a6uGWcxrOvE0M47yoF\n" + "d5+P8AxWIcKS0izDMlDRYq5R/JnmuDj4gx2NQBHgHVFdP/pVYbYYCR5xr5VknOrG\n" + "EWTwqAJ9u8hULldIMnxRZg3WPg7tXjCopkcDZFyJIUWQ4UyRvL2BbnOpFCfmDW04\n" + "3FCdslZmYGxmuV27jJYtiV4NK+24AzHOCv+CA/WyO+Un3mHYj7+iamSwSocjQCij\n" + "8eyWUM2DUC14cZLyiHWwnc0L5GKmpWMR/LS6n8ZnQCytpO+U8PmguuFSLifZax2C\n" + "I+08CwvSvBS+bbFprT4lOmbS0a+fiEUla26+H6Dnsp9tJJQN9MJ1+R9dAgMBAAGj\n" + "ggEzMIIBLzAPBgNVHRMECDAGAQH/AgEAMAsGA1UdDwQEAwIBhjAdBgNVHQ4EFgQU\n" + "VpjcRSUR4owr6tbG4si+LMhp//8wge8GA1UdIwSB5zCB5IAUTX2uDaVeIlpqjxlh\n" + "VLNYy3vAvdqhgcCkgb0wgboxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9y\n" + "bmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMwEQYDVQQKDApMaW5kZW4gTGFi\n" + "MSAwHgYDVQQLDBdTZWNvbmQgTGlmZSBFbmdpbmVlcmluZzEhMB8GA1UEAwwYSW50\n" + "ZWdyYXRpb24gVGVzdCBSb290IENBMSQwIgYJKoZIhvcNAQkBFhVub3JlcGx5QGxp\n" + "bmRlbmxhYi5jb22CCQDvVNj32hjoGTANBgkqhkiG9w0BAQsFAAOCAgEArtAwrDFJ\n" + "IIYLNAFYCJRozDic9xNcRhkz7VRe5EPzWTNcUNmJi+51Z6jHDtEwwk6jLqhkLWqo\n" + "9L2xMty8RkhdGhjY6AuM/ntR2d2540vR+eAiRt03W7LLco6cS9pn3/3OhkkhMU6Z\n" + "ttQ4CxRdrZe6j+IIFYVz60p9AUmvY64t450K1xHCA9MVIZe+PdLqq8yTFphkgHLr\n" + "wngKCWnEK13fMHu+mwI0c2KflbHPCOieV6g3Mc8sjBix1XolkNa2digb4rHPG/Hv\n" + "3S/TB6+B41/8Wuc8qTcNnHhbWNyJVHCkW/+fZDCjhRIyaaUCc9kd/2kf1JeP0KiQ\n" + "jN0uRaGx44qC/PwIQQFRkoeaCXs1w8xIgTkwqfRBOwajBiHMS7wbdliU0eQicH8g\n" + "fnq0+n/oecGMiZ7p43IqQ3JHnrsm7WQsyFT3tJXCxOmL39UQp+2lepSXxHZF42zA\n" + "DqYqdtUdL62ZMsZ79kHgZTcPwB/FmUp1/Wzg8fBYSS2BEMrY6yvDm6nZqfVsbSb9\n" + "uDKSWPRlC9GOAx7VapXURp5l3eWFNuYxdzoaICsHt/GaTo1UIlpUHHJcH7QaWyHt\n" + "Blqa5TwByZuvUGHyKWvsbRm7LgKUyjZx70U58aUlEA6QvKezW6uv8RmIagkvH9Ak\n" + "qGLt2RpliWUWpVXeM+h6gWZykRdeHSJy97g=\n" "-----END CERTIFICATE-----\n" - ); + ); const std::string mPemChildCert( "Certificate:\n" " Data:\n" " Version: 3 (0x2)\n" - " Serial Number: 4096 (0x1000)\n" + " Serial Number: 9e:8d:34:13:e7:9b:f9:31\n" " Signature Algorithm: sha256WithRSAEncryption\n" - " Issuer: C=US, ST=California, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Intermediate CA/emailAddress=noreply@lindenlab.com\n" + " Issuer: C=US, ST=California, L=San Francisco, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Intermediate CA/emailAddress=noreply@lindenlab.com\n" " Validity\n" - " Not Before: May 22 22:58:15 2018 GMT\n" - " Not After : Jul 19 22:58:15 2024 GMT\n" + " Not Before: Jul 23 11:46:39 2024 GMT\n" + " Not After : Jul 21 11:46:39 2034 GMT\n" " Subject: C=US, ST=California, L=San Francisco, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Server Cert/emailAddress=noreply@lindenlab.com\n" " Subject Public Key Info:\n" " Public Key Algorithm: rsaEncryption\n" - " Public-Key: (2048 bit)\n" + " Public-Key: (4096 bit)\n" " Modulus:\n" - " 00:bf:a1:1c:76:82:4a:10:1d:25:0e:02:e2:7a:64:\n" - " 54:c7:94:c5:c0:98:d5:35:f3:cb:cb:30:ba:31:9c:\n" - " bd:4c:2f:4a:4e:24:03:4b:87:5c:c1:5c:fe:d9:89:\n" - " 3b:cb:01:bc:eb:a5:b7:78:dc:b3:58:e5:78:a7:15:\n" - " 34:50:30:aa:16:3a:b2:94:17:6d:1e:7f:b2:70:1e:\n" - " 96:41:bb:1d:e3:22:80:fa:dc:00:6a:fb:34:3e:67:\n" - " e7:c2:21:2f:1b:d3:af:04:49:91:eb:bb:60:e0:26:\n" - " 52:75:28:8a:08:5b:91:56:4e:51:50:40:51:70:af:\n" - " cb:80:66:c8:59:e9:e2:48:a8:62:d0:26:67:80:0a:\n" - " 12:16:d1:f6:15:9e:1f:f5:92:37:f3:c9:2f:03:9e:\n" - " 22:f6:60:5a:76:45:8c:01:2c:99:54:72:19:db:b7:\n" - " 72:e6:5a:69:f3:e9:31:65:5d:0f:c7:5c:9c:17:29:\n" - " 71:14:7f:db:47:c9:1e:65:a2:41:b0:2f:14:17:ec:\n" - " 4b:25:f2:43:8f:b4:a3:8d:37:1a:07:34:b3:29:bb:\n" - " 8a:44:8e:84:08:a2:1b:76:7a:cb:c2:39:2f:6e:e3:\n" - " fc:d6:91:b5:1f:ce:58:91:57:70:35:6e:25:a9:48:\n" - " 0e:07:cf:4e:dd:16:42:65:cf:8a:42:b3:27:e6:fe:\n" - " 6a:e3\n" + " 00:d8:ac:0c:27:8f:ea:c0:4d:21:e4:75:55:31:57:\n" + " 83:46:47:14:1e:f5:67:ae:98:60:c4:97:6d:e8:53:\n" + " f2:4d:3b:ec:6f:08:bc:1e:c0:e2:a6:75:b5:90:1d:\n" + " 30:a2:59:68:32:10:2b:29:67:fc:99:f1:24:6a:36:\n" + " 73:60:31:6b:c7:a0:b8:b0:38:60:b1:59:23:2c:ab:\n" + " 25:a2:c8:b0:bc:2c:c6:d7:4c:87:37:1b:5e:51:a4:\n" + " 63:3e:c4:6d:ed:da:5e:d3:ad:8a:6d:52:e4:87:38:\n" + " 33:76:cf:f2:86:58:b3:10:a4:91:8d:3d:4f:27:9a:\n" + " 8b:b4:d7:67:90:31:1c:f5:7f:78:af:6f:f2:dd:39:\n" + " d0:16:16:7b:46:ad:88:1b:3b:74:6b:10:29:8b:64:\n" + " ba:ed:9f:a7:69:99:55:8f:73:0d:18:a3:7f:40:20:\n" + " 3a:41:4a:94:39:62:8b:fe:c6:9d:79:d0:cd:1c:e2:\n" + " d4:74:bb:43:75:eb:86:8b:30:c1:8d:cc:14:ab:75:\n" + " 2e:f5:3e:0c:05:cb:e4:c3:92:d8:81:8c:df:a5:4e:\n" + " 2e:0b:ae:17:15:9b:e6:dd:9e:16:46:42:27:92:8a:\n" + " 0e:3a:74:1e:d1:3f:ee:7e:a5:d7:ec:1c:63:d4:96:\n" + " 5b:36:f9:15:ee:da:66:ac:5e:de:91:d9:08:24:fb:\n" + " 5d:fc:9b:77:dd:ff:20:a6:67:6f:48:41:5e:5a:ac:\n" + " 13:a4:2c:2a:f2:a3:15:86:e2:84:33:34:e3:91:27:\n" + " 8b:37:ba:b0:c7:5e:1a:0d:b9:f2:4e:0c:55:e6:bb:\n" + " d9:63:f5:05:7b:aa:19:e5:57:ce:a5:b1:46:4b:b3:\n" + " 04:f6:a0:97:26:ed:48:ed:97:93:a6:75:b1:a3:42:\n" + " fc:cc:57:89:da:44:e9:16:a6:30:2c:01:8e:f2:ed:\n" + " be:45:05:08:8a:af:1e:07:51:89:cf:51:4c:aa:f3:\n" + " b3:f0:6f:db:21:80:11:32:0a:23:e2:ff:cc:59:15:\n" + " eb:ff:d2:b8:d6:a1:c1:b4:96:12:82:bf:3f:68:ad:\n" + " c8:61:50:f8:88:4f:d0:be:8e:29:64:1a:16:a5:d9:\n" + " 29:76:16:cd:70:37:c4:f2:1f:4e:c6:57:36:dd:c1:\n" + " 27:19:72:ef:98:7e:34:25:3f:76:b1:ea:15:b2:38:\n" + " 6e:d3:43:03:7a:2b:78:91:9a:19:26:2a:31:b7:5e:\n" + " b7:22:c4:fd:bf:93:10:a4:23:3f:d7:79:53:28:5d:\n" + " 2e:ba:0c:b0:5e:0a:b4:c4:a1:71:75:88:1b:b2:0e:\n" + " 2c:67:08:7b:f0:f6:37:d3:aa:39:50:03:a3:7c:17:\n" + " 1d:52:52:2a:6b:d0:a2:54:2e:ba:11:bc:26:a9:16:\n" + " a6:1b:79\n" " Exponent: 65537 (0x10001)\n" " X509v3 extensions:\n" - " X509v3 Basic Constraints: \n" + " X509v3 Basic Constraints:\n" " CA:FALSE\n" - " Netscape Cert Type: \n" - " SSL Server\n" - " Netscape Comment: \n" - " OpenSSL Generated Server Certificate\n" - " X509v3 Subject Key Identifier: \n" - " BB:59:9F:DE:6B:51:A7:6C:B3:6D:5B:8B:42:F7:B1:65:77:17:A4:E4\n" - " X509v3 Authority Key Identifier: \n" - " keyid:83:21:DE:EC:C0:79:03:6D:1E:83:F3:E5:97:29:D5:5A:C0:96:40:FA\n" - " DirName:/C=US/ST=California/L=San Francisco/O=Linden Lab/OU=Second Life Engineering/CN=Integration Test Root CA/emailAddress=noreply@lindenlab.com\n" - " serial:10:00\n" - "\n" - " X509v3 Key Usage: critical\n" + " X509v3 Key Usage:\n" " Digital Signature, Key Encipherment\n" - " X509v3 Extended Key Usage: \n" + " X509v3 Extended Key Usage:\n" " TLS Web Server Authentication\n" + " X509v3 Subject Key Identifier:\n" + " 7B:1A:F9:2B:C4:B2:F6:AE:D6:F2:8E:B1:73:FB:DD:11:CA:DB:F8:87\n" + " X509v3 Authority Key Identifier:\n" + " keyid:56:98:DC:45:25:11:E2:8C:2B:EA:D6:C6:E2:C8:BE:2C:C8:69:FF:FF\n" + " DirName:/C=US/ST=California/L=San Francisco/O=Linden Lab/OU=Second Life Engineering/CN=Integration Test Root CA/emailAddress=noreply@lindenlab.com\n" + " serial:85:BB:4B:66:26:DB:9A:C6\n" " Signature Algorithm: sha256WithRSAEncryption\n" - " 18:a6:58:55:9b:d4:af:7d:8a:27:d3:28:3a:4c:4b:42:4e:f0:\n" - " 30:d6:d9:95:11:48:12:0a:96:40:d9:2b:21:39:c5:d4:8d:e5:\n" - " 10:bc:68:78:69:0b:9f:15:4a:0b:f1:ab:99:45:0c:20:5f:27:\n" - " df:e7:14:2d:4a:30:f2:c2:8d:37:73:36:1a:27:55:5a:08:5f:\n" - " 71:a1:5e:05:83:b2:59:fe:02:5e:d7:4a:30:15:23:58:04:cf:\n" - " 48:cc:b0:71:88:9c:6b:57:f0:04:0a:d3:a0:64:6b:ee:f3:5f:\n" - " ea:ac:e1:2b:b9:7f:79:b8:db:ce:72:48:72:db:c8:5c:38:72:\n" - " 31:55:d0:ff:6b:bd:73:23:a7:30:18:5d:ed:47:18:0a:67:8e:\n" - " 53:32:0e:99:9b:96:72:45:7f:c6:00:2c:5d:1a:97:53:75:3a:\n" - " 0b:49:3d:3a:00:37:14:67:0c:28:97:34:87:aa:c5:32:e4:ae:\n" - " 34:83:12:4a:10:f7:0e:74:d4:5f:73:bd:ef:0c:b7:d8:0a:7d:\n" - " 8e:8d:5a:48:bd:f4:8e:7b:f9:4a:15:3b:61:c9:5e:40:59:6e:\n" - " c7:a8:a4:02:28:72:c5:54:8c:77:f4:55:a7:86:c0:38:a0:68:\n" - " 19:da:0f:72:5a:a9:7e:69:9f:9c:3a:d6:66:aa:e1:f4:fd:f9:\n" - " b8:4b:6c:71:9e:f0:38:02:c7:6a:9e:dc:e6:fb:ef:23:59:4f:\n" - " 5c:84:0a:df:ea:86:1f:fd:0e:5c:fa:c4:e5:50:1c:10:cf:89:\n" - " 4e:08:0e:4c:4b:61:1a:49:12:f7:e9:4b:17:71:43:7b:6d:b6:\n" - " b5:9f:d4:3b:c7:88:53:48:63:b6:00:80:8f:49:0a:c5:7e:58:\n" - " ac:78:d8:b9:06:b0:bc:86:e2:2e:48:5b:c3:24:fa:aa:72:d8:\n" - " ec:f6:c7:91:9f:0f:c8:b5:fd:2b:b2:a7:bc:2f:40:20:2b:47:\n" - " e0:d1:1d:94:52:6f:6b:be:12:b6:8c:dc:11:db:71:e6:19:ef:\n" - " a8:71:8b:ad:d3:32:c0:1c:a4:3f:b3:0f:af:e5:50:e1:ff:41:\n" - " a4:b7:6f:57:71:af:fd:16:4c:e8:24:b3:99:1b:cf:12:8f:43:\n" - " 05:80:ba:18:19:0a:a5:ec:49:81:41:4c:7e:28:b2:21:f2:59:\n" - " 6e:4a:ed:de:f9:fa:99:85:60:1f:e6:c2:42:5c:08:00:3c:84:\n" - " 06:a9:24:d4:cf:7b:6e:1b:59:1d:f4:70:16:03:a1:e0:0b:00:\n" - " 95:5c:39:03:fc:9d:1c:8e:f7:59:0c:61:47:f6:7f:07:22:48:\n" - " 83:40:ac:e1:98:5f:c7:be:05:d5:29:2b:bf:0d:03:0e:e9:5e:\n" - " 2b:dd:09:18:fe:5e:30:61\n" + " ad:7c:50:12:24:62:62:83:e9:dd:81:1a:12:1c:6d:ae:1e:a6:\n" + " 01:cc:93:8b:ac:83:7c:3d:57:d7:7f:d2:13:40:82:c7:27:07:\n" + " 31:d8:c4:01:04:64:9c:dc:ae:7b:52:bd:f5:62:7a:d0:7c:13:\n" + " 1a:19:86:6a:ce:9a:ba:69:07:77:75:b6:67:56:d0:c3:8d:6f:\n" + " 59:5f:ac:31:83:32:2c:4f:8c:85:8c:f3:56:5b:e0:83:16:19:\n" + " c9:55:4d:56:2c:e0:06:f8:71:85:4b:7e:c6:20:b3:f6:5b:85:\n" + " 6a:b7:0f:0e:0c:75:38:6a:aa:53:cc:b0:bf:c1:fd:a1:01:8a:\n" + " 7e:5a:0b:4d:51:fc:1b:14:b0:8d:62:17:b7:5d:6a:64:30:80:\n" + " aa:50:9a:23:9e:19:46:11:9d:49:d1:35:81:87:80:8c:9c:71:\n" + " 61:26:07:23:5d:a7:ea:4e:0c:53:77:bd:eb:18:6d:63:8b:2c:\n" + " e1:83:bb:bb:f8:3e:7c:e8:0d:19:1e:be:35:aa:99:0f:c7:25:\n" + " 0c:a8:f9:74:02:c8:4c:8e:bb:13:18:fd:aa:21:34:bc:2d:9f:\n" + " 10:96:e2:99:e3:9a:d7:91:0e:1e:77:20:70:e9:b4:63:25:f8:\n" + " ea:14:1f:24:b0:6a:8b:2a:f4:61:b1:0d:7d:18:bc:1d:6d:04:\n" + " 11:b2:9f:a2:a7:55:be:2b:2c:2f:c1:d8:95:13:73:af:1c:96:\n" + " 49:30:9c:9c:94:81:6c:9b:a7:87:5c:cf:46:95:95:4a:6f:bf:\n" + " df:c9:3d:74:3e:24:6e:44:1e:14:8b:68:23:e4:00:b5:a5:b7:\n" + " 5b:a9:ea:16:5f:fa:b1:d3:1a:b1:9b:36:ef:a4:7a:6f:a3:b0:\n" + " 97:35:ac:70:c0:cc:8e:a2:d3:40:0e:c1:70:0b:d5:ce:cd:51:\n" + " 82:8a:40:72:04:8d:62:af:ba:a8:e7:a8:e9:b9:99:b7:5c:5d:\n" + " 27:96:b2:3d:f9:0d:26:8c:3f:db:ac:86:97:be:f1:2c:0b:ca:\n" + " 90:07:93:96:f4:75:c3:e8:4c:f6:a8:a2:3f:da:11:21:e7:b1:\n" + " 8c:62:36:ae:91:a9:2a:73:ba:67:f5:24:16:c3:ee:b7:b1:b4:\n" + " e3:8a:28:23:84:cf:38:c6:f0:8e:21:f6:b8:76:9a:6d:d1:e3:\n" + " 74:81:7a:22:20:a0:82:2a:31:8a:ba:44:0b:61:5a:aa:ba:c6:\n" + " 07:99:36:0a:24:06:2f:8e:c1:1c:4b:f0:65:72:fb:e9:b5:31:\n" + " 59:13:2c:c6:f8:5b:91:e2:d8:96:f3:1a:06:0b:2a:62:12:4d:\n" + " 5e:65:c9:e9:e4:00:99:a6:d3:60:1f:c3:d6:cc:a6:9b:a5:14:\n" + " 1b:4d:db:e7:3d:52:7e:2c\n" "-----BEGIN CERTIFICATE-----\n" - "MIIGbjCCBFagAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgaoxCzAJBgNVBAYTAlVT\n" - "MRMwEQYDVQQIDApDYWxpZm9ybmlhMRMwEQYDVQQKDApMaW5kZW4gTGFiMSAwHgYD\n" - "VQQLDBdTZWNvbmQgTGlmZSBFbmdpbmVlcmluZzEpMCcGA1UEAwwgSW50ZWdyYXRp\n" - "b24gVGVzdCBJbnRlcm1lZGlhdGUgQ0ExJDAiBgkqhkiG9w0BCQEWFW5vcmVwbHlA\n" - "bGluZGVubGFiLmNvbTAeFw0xODA1MjIyMjU4MTVaFw0yNDA3MTkyMjU4MTVaMIG+\n" - "MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2Fu\n" - "IEZyYW5jaXNjbzETMBEGA1UECgwKTGluZGVuIExhYjEgMB4GA1UECwwXU2Vjb25k\n" - "IExpZmUgRW5naW5lZXJpbmcxJTAjBgNVBAMMHEludGVncmF0aW9uIFRlc3QgU2Vy\n" - "dmVyIENlcnQxJDAiBgkqhkiG9w0BCQEWFW5vcmVwbHlAbGluZGVubGFiLmNvbTCC\n" - "ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL+hHHaCShAdJQ4C4npkVMeU\n" - "xcCY1TXzy8swujGcvUwvSk4kA0uHXMFc/tmJO8sBvOult3jcs1jleKcVNFAwqhY6\n" - "spQXbR5/snAelkG7HeMigPrcAGr7ND5n58IhLxvTrwRJkeu7YOAmUnUoighbkVZO\n" - "UVBAUXCvy4BmyFnp4kioYtAmZ4AKEhbR9hWeH/WSN/PJLwOeIvZgWnZFjAEsmVRy\n" - "Gdu3cuZaafPpMWVdD8dcnBcpcRR/20fJHmWiQbAvFBfsSyXyQ4+0o403Ggc0sym7\n" - "ikSOhAiiG3Z6y8I5L27j/NaRtR/OWJFXcDVuJalIDgfPTt0WQmXPikKzJ+b+auMC\n" - "AwEAAaOCAYYwggGCMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZAMDMGCWCG\n" - "SAGG+EIBDQQmFiRPcGVuU1NMIEdlbmVyYXRlZCBTZXJ2ZXIgQ2VydGlmaWNhdGUw\n" - "HQYDVR0OBBYEFLtZn95rUadss21bi0L3sWV3F6TkMIHoBgNVHSMEgeAwgd2AFIMh\n" - "3uzAeQNtHoPz5Zcp1VrAlkD6oYHApIG9MIG6MQswCQYDVQQGEwJVUzETMBEGA1UE\n" - "CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzETMBEGA1UECgwK\n" - "TGluZGVuIExhYjEgMB4GA1UECwwXU2Vjb25kIExpZmUgRW5naW5lZXJpbmcxITAf\n" - "BgNVBAMMGEludGVncmF0aW9uIFRlc3QgUm9vdCBDQTEkMCIGCSqGSIb3DQEJARYV\n" - "bm9yZXBseUBsaW5kZW5sYWIuY29tggIQADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0l\n" - "BAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggIBABimWFWb1K99iifTKDpM\n" - "S0JO8DDW2ZURSBIKlkDZKyE5xdSN5RC8aHhpC58VSgvxq5lFDCBfJ9/nFC1KMPLC\n" - "jTdzNhonVVoIX3GhXgWDsln+Al7XSjAVI1gEz0jMsHGInGtX8AQK06Bka+7zX+qs\n" - "4Su5f3m4285ySHLbyFw4cjFV0P9rvXMjpzAYXe1HGApnjlMyDpmblnJFf8YALF0a\n" - "l1N1OgtJPToANxRnDCiXNIeqxTLkrjSDEkoQ9w501F9zve8Mt9gKfY6NWki99I57\n" - "+UoVO2HJXkBZbseopAIocsVUjHf0VaeGwDigaBnaD3JaqX5pn5w61maq4fT9+bhL\n" - "bHGe8DgCx2qe3Ob77yNZT1yECt/qhh/9Dlz6xOVQHBDPiU4IDkxLYRpJEvfpSxdx\n" - "Q3tttrWf1DvHiFNIY7YAgI9JCsV+WKx42LkGsLyG4i5IW8Mk+qpy2Oz2x5GfD8i1\n" - "/Suyp7wvQCArR+DRHZRSb2u+EraM3BHbceYZ76hxi63TMsAcpD+zD6/lUOH/QaS3\n" - "b1dxr/0WTOgks5kbzxKPQwWAuhgZCqXsSYFBTH4osiHyWW5K7d75+pmFYB/mwkJc\n" - "CAA8hAapJNTPe24bWR30cBYDoeALAJVcOQP8nRyO91kMYUf2fwciSINArOGYX8e+\n" - "BdUpK78NAw7pXivdCRj+XjBh\n" + "MIIHSTCCBTGgAwIBAgIJAJ6NNBPnm/kxMA0GCSqGSIb3DQEBCwUAMIHCMQswCQYD\n" + "VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j\n" + "aXNjbzETMBEGA1UECgwKTGluZGVuIExhYjEgMB4GA1UECwwXU2Vjb25kIExpZmUg\n" + "RW5naW5lZXJpbmcxKTAnBgNVBAMMIEludGVncmF0aW9uIFRlc3QgSW50ZXJtZWRp\n" + "YXRlIENBMSQwIgYJKoZIhvcNAQkBFhVub3JlcGx5QGxpbmRlbmxhYi5jb20wHhcN\n" + "MjQwNzIzMTE0NjM5WhcNMzQwNzIxMTE0NjM5WjCBvjELMAkGA1UEBhMCVVMxEzAR\n" + "BgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEzARBgNV\n" + "BAoMCkxpbmRlbiBMYWIxIDAeBgNVBAsMF1NlY29uZCBMaWZlIEVuZ2luZWVyaW5n\n" + "MSUwIwYDVQQDDBxJbnRlZ3JhdGlvbiBUZXN0IFNlcnZlciBDZXJ0MSQwIgYJKoZI\n" + "hvcNAQkBFhVub3JlcGx5QGxpbmRlbmxhYi5jb20wggIiMA0GCSqGSIb3DQEBAQUA\n" + "A4ICDwAwggIKAoICAQDYrAwnj+rATSHkdVUxV4NGRxQe9WeumGDEl23oU/JNO+xv\n" + "CLwewOKmdbWQHTCiWWgyECspZ/yZ8SRqNnNgMWvHoLiwOGCxWSMsqyWiyLC8LMbX\n" + "TIc3G15RpGM+xG3t2l7TrYptUuSHODN2z/KGWLMQpJGNPU8nmou012eQMRz1f3iv\n" + "b/LdOdAWFntGrYgbO3RrECmLZLrtn6dpmVWPcw0Yo39AIDpBSpQ5Yov+xp150M0c\n" + "4tR0u0N164aLMMGNzBSrdS71PgwFy+TDktiBjN+lTi4LrhcVm+bdnhZGQieSig46\n" + "dB7RP+5+pdfsHGPUlls2+RXu2masXt6R2Qgk+138m3fd/yCmZ29IQV5arBOkLCry\n" + "oxWG4oQzNOORJ4s3urDHXhoNufJODFXmu9lj9QV7qhnlV86lsUZLswT2oJcm7Ujt\n" + "l5OmdbGjQvzMV4naROkWpjAsAY7y7b5FBQiKrx4HUYnPUUyq87Pwb9shgBEyCiPi\n" + "/8xZFev/0rjWocG0lhKCvz9orchhUPiIT9C+jilkGhal2Sl2Fs1wN8TyH07GVzbd\n" + "wScZcu+YfjQlP3ax6hWyOG7TQwN6K3iRmhkmKjG3XrcixP2/kxCkIz/XeVMoXS66\n" + "DLBeCrTEoXF1iBuyDixnCHvw9jfTqjlQA6N8Fx1SUipr0KJULroRvCapFqYbeQID\n" + "AQABo4IBQjCCAT4wCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYI\n" + "KwYBBQUHAwEwHQYDVR0OBBYEFHsa+SvEsvau1vKOsXP73RHK2/iHMIHvBgNVHSME\n" + "gecwgeSAFFaY3EUlEeKMK+rWxuLIvizIaf//oYHApIG9MIG6MQswCQYDVQQGEwJV\n" + "UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzET\n" + "MBEGA1UECgwKTGluZGVuIExhYjEgMB4GA1UECwwXU2Vjb25kIExpZmUgRW5naW5l\n" + "ZXJpbmcxITAfBgNVBAMMGEludGVncmF0aW9uIFRlc3QgUm9vdCBDQTEkMCIGCSqG\n" + "SIb3DQEJARYVbm9yZXBseUBsaW5kZW5sYWIuY29tggkAhbtLZibbmsYwDQYJKoZI\n" + "hvcNAQELBQADggIBAK18UBIkYmKD6d2BGhIcba4epgHMk4usg3w9V9d/0hNAgscn\n" + "BzHYxAEEZJzcrntSvfVietB8ExoZhmrOmrppB3d1tmdW0MONb1lfrDGDMixPjIWM\n" + "81Zb4IMWGclVTVYs4Ab4cYVLfsYgs/ZbhWq3Dw4MdThqqlPMsL/B/aEBin5aC01R\n" + "/BsUsI1iF7ddamQwgKpQmiOeGUYRnUnRNYGHgIyccWEmByNdp+pODFN3vesYbWOL\n" + "LOGDu7v4PnzoDRkevjWqmQ/HJQyo+XQCyEyOuxMY/aohNLwtnxCW4pnjmteRDh53\n" + "IHDptGMl+OoUHySwaosq9GGxDX0YvB1tBBGyn6KnVb4rLC/B2JUTc68clkkwnJyU\n" + "gWybp4dcz0aVlUpvv9/JPXQ+JG5EHhSLaCPkALWlt1up6hZf+rHTGrGbNu+kem+j\n" + "sJc1rHDAzI6i00AOwXAL1c7NUYKKQHIEjWKvuqjnqOm5mbdcXSeWsj35DSaMP9us\n" + "hpe+8SwLypAHk5b0dcPoTPaooj/aESHnsYxiNq6RqSpzumf1JBbD7rextOOKKCOE\n" + "zzjG8I4h9rh2mm3R43SBeiIgoIIqMYq6RAthWqq6xgeZNgokBi+OwRxL8GVy++m1\n" + "MVkTLMb4W5Hi2JbzGgYLKmISTV5lyenkAJmm02Afw9bMppulFBtN2+c9Un4s\n" "-----END CERTIFICATE-----\n" - ); + ); + // Test wrapper declaration : wrapping nothing for the moment struct sechandler_basic_test @@ -701,14 +722,13 @@ namespace tut //std::ostringstream llsd_value; //llsd_value << LLSDOStreamer(llsd_cert) << std::endl; LL_DEBUGS() << "test 1 cert " << llsd_cert << LL_ENDL; - ensure_equals("Issuer Name/commonName", (std::string)llsd_cert["issuer_name"]["commonName"], "Integration Test Intermediate CA"); ensure_equals("Issuer Name/countryName", (std::string)llsd_cert["issuer_name"]["countryName"], "US"); ensure_equals("Issuer Name/state", (std::string)llsd_cert["issuer_name"]["stateOrProvinceName"], "California"); ensure_equals("Issuer Name/org name", (std::string)llsd_cert["issuer_name"]["organizationName"], "Linden Lab"); ensure_equals("Issuer Name/org unit", (std::string)llsd_cert["issuer_name"]["organizationalUnitName"], "Second Life Engineering"); ensure_equals("Issuer name string", (std::string)llsd_cert["issuer_name_string"], - "emailAddress=noreply@lindenlab.com,CN=Integration Test Intermediate CA,OU=Second Life Engineering,O=Linden Lab,ST=California,C=US"); + "emailAddress=noreply@lindenlab.com,CN=Integration Test Intermediate CA,OU=Second Life Engineering,O=Linden Lab,L=San Francisco,ST=California,C=US"); ensure_equals("subject Name/commonName", (std::string)llsd_cert["subject_name"]["commonName"], "Integration Test Server Cert"); ensure_equals("subject Name/countryName", (std::string)llsd_cert["subject_name"]["countryName"], "US"); @@ -721,9 +741,9 @@ namespace tut ensure_equals("subject name string", (std::string)llsd_cert["subject_name_string"], "emailAddress=noreply@lindenlab.com,CN=Integration Test Server Cert,OU=Second Life Engineering,O=Linden Lab,L=San Francisco,ST=California,C=US"); - ensure_equals("serial number", (std::string)llsd_cert["serial_number"], "1000"); - ensure_equals("valid from", (std::string)llsd_cert["valid_from"], "2018-05-22T22:58:15Z"); - ensure_equals("valid to", (std::string)llsd_cert["valid_to"], "2024-07-19T22:58:15Z"); + ensure_equals("serial number", (std::string)llsd_cert["serial_number"], "9E8D3413E79BF931"); + ensure_equals("valid from", (std::string)llsd_cert["valid_from"], "2024-07-23T11:46:39Z"); + ensure_equals("valid to", (std::string)llsd_cert["valid_to"], "2034-07-21T11:46:39Z"); LLSD expectedKeyUsage = LLSD::emptyArray(); expectedKeyUsage.append(LLSD((std::string)"digitalSignature")); expectedKeyUsage.append(LLSD((std::string)"keyEncipherment")); @@ -1042,7 +1062,7 @@ namespace tut //validate find LLSD find_info = LLSD::emptyMap(); - find_info["subjectKeyIdentifier"] = "bb:59:9f:de:6b:51:a7:6c:b3:6d:5b:8b:42:f7:b1:65:77:17:a4:e4"; + find_info["subjectKeyIdentifier"] = "7b:1a:f9:2b:c4:b2:f6:ae:d6:f2:8e:b1:73:fb:dd:11:ca:db:f8:87"; LLBasicCertificateVector::iterator found_cert = test_vector->find(find_info); ensure("found some cert", found_cert != test_vector->end()); X509* found_x509 = (*found_cert).get()->getOpenSSLX509(); @@ -1225,7 +1245,7 @@ namespace tut X509_STORE_CTX_set0_untrusted(test_store, NULL); test_chain = new LLBasicCertificateChain(test_store); X509_STORE_CTX_free(test_store); - ensure_equals("two elements in store", test_chain->size(), 1); + ensure_equals("two elements in store [1]", test_chain->size(), 1); X509* test_cert = (*test_chain)[0]->getOpenSSLX509(); ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); X509_free(test_cert); @@ -1238,7 +1258,7 @@ namespace tut sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509IntermediateCert); test_chain = new LLBasicCertificateChain(test_store); X509_STORE_CTX_free(test_store); - ensure_equals("two elements in store", test_chain->size(), 2); + ensure_equals("two elements in store [2]", test_chain->size(), 2); test_cert = (*test_chain)[0]->getOpenSSLX509(); ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); X509_free(test_cert); @@ -1254,7 +1274,7 @@ namespace tut sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509TestCert); test_chain = new LLBasicCertificateChain(test_store); X509_STORE_CTX_free(test_store); - ensure_equals("two elements in store", test_chain->size(), 1); + ensure_equals("two elements in store [3]", test_chain->size(), 1); test_cert = (*test_chain)[0]->getOpenSSLX509(); ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); X509_free(test_cert); @@ -1267,7 +1287,7 @@ namespace tut sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509TestCert); test_chain = new LLBasicCertificateChain(test_store); X509_STORE_CTX_free(test_store); - ensure_equals("two elements in store", test_chain->size(), 2); + ensure_equals("two elements in store [4]", test_chain->size(), 2); test_cert = (*test_chain)[0]->getOpenSSLX509(); ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); X509_free(test_cert); -- cgit v1.2.3 From ffeb738afda99fca98a9c1ba7704d7f6144da70c Mon Sep 17 00:00:00 2001 From: nat-goodspeed Date: Wed, 31 Jul 2024 06:30:19 -0400 Subject: Represent the many "LLAgent" "setCameraParams" args in an array. This encapsulates the boilerplate associated with passing each distinct parameter to its corresponding LLFollowCamMgr method. --- indra/newview/llagentlistener.cpp | 106 +++++++++++----------- indra/newview/scripts/lua/test_camera_control.lua | 2 +- 2 files changed, 55 insertions(+), 53 deletions(-) (limited to 'indra') diff --git a/indra/newview/llagentlistener.cpp b/indra/newview/llagentlistener.cpp index 045367dbdd..80460666a5 100644 --- a/indra/newview/llagentlistener.cpp +++ b/indra/newview/llagentlistener.cpp @@ -45,6 +45,7 @@ #include "lltoolgrab.h" #include "llhudeffectlookat.h" #include "llagentcamera.h" +#include LLAgentListener::LLAgentListener(LLAgent &agent) : LLEventAPI("LLAgent", @@ -522,62 +523,63 @@ void LLAgentListener::getGroups(const LLSD& event) const sendReply(LLSDMap("groups", reply), event); } +/*----------------------------- camera control -----------------------------*/ +// specialize LLSDParam to support (const LLVector3&) arguments -- this +// wouldn't even be necessary except that the relevant LLVector3 constructor +// is explicitly explicit +template <> +class LLSDParam: public LLSDParamBase +{ +public: + LLSDParam(const LLSD& value): value(LLVector3(value)) {} + + operator const LLVector3&() const { return value; } + +private: + LLVector3 value; +}; + +// accept any of a number of similar LLFollowCamMgr methods with different +// argument types, and return a wrapper lambda that accepts LLSD and converts +// to the target argument type +template +auto wrap(void (LLFollowCamMgr::*method)(const LLUUID& source, T arg)) +{ + return [method](LLFollowCamMgr& followcam, const LLUUID& source, const LLSD& arg) + { (followcam.*method)(source, LLSDParam(arg)); }; +} + +// table of supported LLFollowCamMgr methods, +// with the corresponding setFollowCamParams() argument keys +static std::pair> +cam_params[] = +{ + { "camera_pos", wrap(&LLFollowCamMgr::setPosition) }, + { "focus_pos", wrap(&LLFollowCamMgr::setFocus) }, + { "focus_offset", wrap(&LLFollowCamMgr::setFocusOffset) }, + { "camera_locked", wrap(&LLFollowCamMgr::setPositionLocked) }, + { "focus_locked", wrap(&LLFollowCamMgr::setFocusLocked) }, + { "distance", wrap(&LLFollowCamMgr::setDistance) }, + { "focus_threshold", wrap(&LLFollowCamMgr::setFocusThreshold) }, + { "camera_threshold", wrap(&LLFollowCamMgr::setPositionThreshold) }, + { "focus_lag", wrap(&LLFollowCamMgr::setFocusLag) }, + { "camera_lag", wrap(&LLFollowCamMgr::setPositionLag) }, + { "camera_pitch", wrap(&LLFollowCamMgr::setPitch) }, + { "behindness_lag", wrap(&LLFollowCamMgr::setBehindnessLag) }, + { "behindness_angle", wrap(&LLFollowCamMgr::setBehindnessAngle) }, +}; + void LLAgentListener::setFollowCamParams(const LLSD& event) const { - if (event.has("camera_pos")) + auto& followcam{ LLFollowCamMgr::instance() }; + for (const auto& pair : cam_params) { - LLFollowCamMgr::getInstance()->setPosition(gAgentID, LLVector3(event["camera_pos"])); - } - if (event.has("focus_pos")) - { - LLFollowCamMgr::getInstance()->setFocus(gAgentID, LLVector3(event["focus_pos"])); - } - if (event.has("focus_offset")) - { - LLFollowCamMgr::getInstance()->setFocusOffset(gAgentID, LLVector3(event["focus_offset"])); - } - if (event.has("camera_locked")) - { - LLFollowCamMgr::getInstance()->setPositionLocked(gAgentID, event["camera_locked"]); - } - if (event.has("focus_locked")) - { - LLFollowCamMgr::getInstance()->setFocusLocked(gAgentID, event["focus_locked"]); - } - if (event.has("distance")) - { - LLFollowCamMgr::getInstance()->setDistance(gAgentID, event["distance"].asReal()); - } - if (event.has("focus_threshold")) - { - LLFollowCamMgr::getInstance()->setFocusThreshold(gAgentID, event["focus_threshold"].asReal()); - } - if (event.has("camera_threshold")) - { - LLFollowCamMgr::getInstance()->setPositionThreshold(gAgentID, event["camera_threshold"].asReal()); - } - if (event.has("focus_lag")) - { - LLFollowCamMgr::getInstance()->setFocusLag(gAgentID, event["focus_lag"].asReal()); - } - if (event.has("camera_lag")) - { - LLFollowCamMgr::getInstance()->setPositionLag(gAgentID, event["camera_lag"].asReal()); - } - if (event.has("camera_pitch")) - { - LLFollowCamMgr::getInstance()->setPitch(gAgentID, event["camera_pitch"].asReal()); - } - if (event.has("behindness_lag")) - { - LLFollowCamMgr::getInstance()->setBehindnessLag(gAgentID, event["behindness_lag"].asReal()); - } - if (event.has("behindness_angle")) - { - LLFollowCamMgr::getInstance()->setBehindnessAngle(gAgentID, event["behindness_angle"].asReal()); + if (event.has(pair.first)) + { + pair.second(followcam, gAgentID, event[pair.first]); + } } - - LLFollowCamMgr::getInstance()->setCameraActive(gAgentID, true); + followcam.setCameraActive(gAgentID, true); } void LLAgentListener::setFollowCamActive(LLSD const & event) const diff --git a/indra/newview/scripts/lua/test_camera_control.lua b/indra/newview/scripts/lua/test_camera_control.lua index e7ad69b473..9b35cdf8cd 100644 --- a/indra/newview/scripts/lua/test_camera_control.lua +++ b/indra/newview/scripts/lua/test_camera_control.lua @@ -45,5 +45,5 @@ function flt:commit_reset_btn(event_data) LLAgent.setFollowCamActive(false) end -startup.wait('STATE_LOGIN_WAIT') +startup.wait('STATE_STARTED') flt:show() -- cgit v1.2.3 From 3214c7bd7e77fdd458d64ec101a8a67287b59ffa Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 1 Aug 2024 16:09:11 -0400 Subject: Add lua_push(), lua_to(), lua_[gs]etfieldv(), lua_raw[gs]etfield(). Leverage C++ overloads to allow use of generic function names disambiguated by argument type. This allows using templates for certain common operation sequences. --- indra/llcommon/lua_function.cpp | 17 ++--- indra/llcommon/lua_function.h | 162 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 11 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 42bba80ed5..b173d17ede 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -32,6 +32,8 @@ #include "lualistener.h" #include "stringize.h" +using namespace std::literals; // e.g. std::string_view literals: "this"sv + const S32 INTERRUPTS_MAX_LIMIT = 20000; const S32 INTERRUPTS_SUSPEND_LIMIT = 100; @@ -41,7 +43,7 @@ const S32 INTERRUPTS_SUSPEND_LIMIT = 100; int DistinctInt::mValues{0}; /***************************************************************************** -* luau namespace +* lluau namespace *****************************************************************************/ namespace { @@ -93,21 +95,14 @@ fsyspath lluau::source_path(lua_State* L) void lluau::set_interrupts_counter(lua_State *L, S32 counter) { - luaL_checkstack(L, 2, nullptr); - lua_pushstring(L, "_INTERRUPTS"); - lua_pushinteger(L, counter); - lua_rawset(L, LUA_REGISTRYINDEX); + lua_rawsetfield(L, LUA_REGISTRYINDEX, "_INTERRUPTS"sv, lua_Integer(counter)); } void lluau::check_interrupts_counter(lua_State* L) { - luaL_checkstack(L, 1, nullptr); - lua_pushstring(L, "_INTERRUPTS"); - lua_rawget(L, LUA_REGISTRYINDEX); - S32 counter = lua_tointeger(L, -1); - lua_pop(L, 1); + auto counter = lua_rawgetfield(L, LUA_REGISTRYINDEX, "_INTERRUPTS"sv); - lluau::set_interrupts_counter(L, ++counter); + set_interrupts_counter(L, ++counter); if (counter > INTERRUPTS_MAX_LIMIT) { lluau::error(L, "Possible infinite loop, terminated."); diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 7f7a7566f3..7b59af30f5 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -18,6 +18,7 @@ #include "luau/lualib.h" #include "fsyspath.h" #include "llerror.h" +#include "llsd.h" #include "stringize.h" #include // std::uncaught_exceptions() #include // std::shared_ptr @@ -168,6 +169,167 @@ private: int mIndex; }; +/***************************************************************************** +* lua_push() wrappers for generic code +*****************************************************************************/ +inline +void lua_push(lua_State* L, bool b) +{ + lua_pushboolean(L, int(b)); +} + +inline +void lua_push(lua_State* L, lua_CFunction fn) +{ + lua_pushcfunction(L, fn, ""); +} + +inline +void lua_push(lua_State* L, lua_Integer n) +{ + lua_pushinteger(L, n); +} + +inline +void lua_push(lua_State* L, void* p) +{ + lua_pushlightuserdata(L, p); +} + +inline +void lua_push(lua_State* L, const LLSD& data) +{ + lua_pushllsd(L, data); +} + +inline +void lua_push(lua_State* L, const char* s, size_t len) +{ + lua_pushlstring(L, s, len); +} + +inline +void lua_push(lua_State* L) +{ + lua_pushnil(L); +} + +inline +void lua_push(lua_State* L, lua_Number n) +{ + lua_pushnumber(L, n); +} + +inline +void lua_push(lua_State* L, const std::string& s) +{ + lua_pushstdstring(L, s); +} + +inline +void lua_push(lua_State* L, const char* s) +{ + lua_pushstring(L, s); +} + +/***************************************************************************** +* lua_to() wrappers for generic code +*****************************************************************************/ +template +auto lua_to(lua_State* L, int index); + +template <> +inline +auto lua_to(lua_State* L, int index) +{ + return lua_toboolean(L, index); +} + +template <> +inline +auto lua_to(lua_State* L, int index) +{ + return lua_tocfunction(L, index); +} + +template <> +inline +auto lua_to(lua_State* L, int index) +{ + return lua_tointeger(L, index); +} + +template <> +inline +auto lua_to(lua_State* L, int index) +{ + return lua_tollsd(L, index); +} + +template <> +inline +auto lua_to(lua_State* L, int index) +{ + return lua_tonumber(L, index); +} + +template <> +inline +auto lua_to(lua_State* L, int index) +{ + return lua_tostdstring(L, index); +} + +template <> +inline +auto lua_to(lua_State* L, int index) +{ + return lua_touserdata(L, index); +} + +/***************************************************************************** +* field operations +*****************************************************************************/ +// return to C++, from table at index, the value of field k +template +auto lua_getfieldv(lua_State* L, int index, const char* k) +{ + luaL_checkstack(L, 1, nullptr); + lua_getfield(L, index, k); + LuaPopper pop(L, 1); + return lua_to(L, -1); +} + +// set in table at index, as field k, the specified C++ value +template +auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value) +{ + luaL_checkstack(L, 1, nullptr); + lua_push(L, value); + lua_setfield(L, index, k); +} + +// return to C++, from table at index, the value of field k (without metamethods) +template +auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) +{ + luaL_checkstack(L, 1, nullptr); + lua_pushlstring(L, k.data(), k.length()); + lua_rawget(L, index); + LuaPopper pop(L, 1); + return lua_to(L, -1); +} + +// set in table at index, as field k, the specified C++ value (without metamethods) +template +void lua_rawsetfield(lua_State* L, int index, const std::string_view& k, const T& value) +{ + luaL_checkstack(L, 2, nullptr); + lua_pushlstring(L, k.data(), k.length()); + lua_push(L, value); + lua_rawset(L, index); +} + /***************************************************************************** * lua_function (and helper class LuaFunction) *****************************************************************************/ -- cgit v1.2.3 From fdb7207aa0d1f25ed3e14fbc4e8615e8383e508c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 1 Aug 2024 16:52:44 -0400 Subject: Add UI.callables() and corresponding entry point. --- indra/newview/lluilistener.cpp | 43 ++++++++++++++++++++++++++++ indra/newview/lluilistener.h | 1 + indra/newview/scripts/lua/require/UI.lua | 6 ++++ indra/newview/scripts/lua/test_callables.lua | 6 ++++ 4 files changed, 56 insertions(+) create mode 100644 indra/newview/scripts/lua/test_callables.lua (limited to 'indra') diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp index c73c93859a..2b2187a73b 100644 --- a/indra/newview/lluilistener.cpp +++ b/indra/newview/lluilistener.cpp @@ -55,6 +55,13 @@ LLUIListener::LLUIListener(): &LLUIListener::call, llsd::map("function", LLSD(), "reply", LLSD())); + add("callables", + "Return a list [\"callables\"] of dicts {name, access} of functions registered to\n" + "invoke with \"call\".\n" + "access has values \"allow\", \"block\" or \"throttle\".", + &LLUIListener::callables, + llsd::map("reply", LLSD::String())); + add("getValue", "For the UI control identified by the path in [\"path\"], return the control's\n" "current value as [\"value\"] reply.", @@ -131,6 +138,42 @@ void LLUIListener::call(const LLSD& event) (info->callback_func)(NULL, event["parameter"]); } +void LLUIListener::callables(const LLSD& event) const +{ + Response response(LLSD(), event); + + using Registry = LLUICtrl::CommitCallbackRegistry; + using Method = Registry::Registrar& (*)(); + static Method registrars[] = + { + &Registry::defaultRegistrar, + &Registry::currentRegistrar, + }; + LLSD list; + for (auto method : registrars) + { + auto& registrar{ (*method)() }; + for (auto it = registrar.beginItems(), end = registrar.endItems(); it != end; ++it) + { + LLSD entry{ llsd::map("name", it->first) }; + switch (it->second.handle_untrusted) + { + case LLUICtrl::CommitCallbackInfo::UNTRUSTED_ALLOW: + entry["access"] = "allow"; + break; + case LLUICtrl::CommitCallbackInfo::UNTRUSTED_BLOCK: + entry["access"] = "block"; + break; + case LLUICtrl::CommitCallbackInfo::UNTRUSTED_THROTTLE: + entry["access"] = "throttle"; + break; + } + list.append(entry); + } + } + response["callables"] = list; +} + void LLUIListener::getValue(const LLSD&event) const { Response response(LLSD(), event); diff --git a/indra/newview/lluilistener.h b/indra/newview/lluilistener.h index cbb5014300..c90253ab59 100644 --- a/indra/newview/lluilistener.h +++ b/indra/newview/lluilistener.h @@ -41,6 +41,7 @@ public: private: void call(const LLSD& event); + void callables(const LLSD& event) const; void getValue(const LLSD&event) const; void addMenu(const LLSD&event) const; diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua index 28488ff3e1..06b49c6269 100644 --- a/indra/newview/scripts/lua/require/UI.lua +++ b/indra/newview/scripts/lua/require/UI.lua @@ -14,6 +14,10 @@ function UI.call(func, parameter) leap.request('UI', {op='call', ['function']=func, parameter=parameter}) end +function UI.callables() + return leap.request('UI', {op='callables'}).callables +end + function UI.getValue(path) return leap.request('UI', {op='getValue', path=path})['value'] end @@ -152,6 +156,7 @@ function UI.addMenuBranch(...) return leap.request('UI', args) end +-- see UI.callables() for valid values of 'func' function UI.addMenuItem(...) local args = mapargs('name,label,parent_menu,func,param', ...) args.op = 'addMenuItem' @@ -163,4 +168,5 @@ function UI.addMenuSeparator(...) args.op = 'addMenuSeparator' return leap.request('UI', args) end + return UI diff --git a/indra/newview/scripts/lua/test_callables.lua b/indra/newview/scripts/lua/test_callables.lua new file mode 100644 index 0000000000..1bee062db8 --- /dev/null +++ b/indra/newview/scripts/lua/test_callables.lua @@ -0,0 +1,6 @@ +startup=require 'startup' +UI=require 'UI' +startup.wait('STATE_LOGIN_WAIT') +for _, cbl in pairs(UI.callables()) do + print(`{cbl.name} ({cbl.access})`) +end -- cgit v1.2.3 From c8ba633a4564da29d9b58b0e08ffb590bb3bce5f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 2 Aug 2024 10:12:57 -0400 Subject: Add 'UI' 'getParents' op to list top-menu 'parent_menu' names. --- indra/newview/lluilistener.cpp | 15 ++++++++++++++- indra/newview/lluilistener.h | 3 ++- 2 files changed, 16 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp index 2b2187a73b..b81859a764 100644 --- a/indra/newview/lluilistener.cpp +++ b/indra/newview/lluilistener.cpp @@ -68,6 +68,11 @@ LLUIListener::LLUIListener(): &LLUIListener::getValue, llsd::map("path", LLSD(), "reply", LLSD())); + add("getParents", + "List names of Top menus suitable for passing as \"parent_menu\"", + &LLUIListener::getParents, + llsd::map("reply", LLSD::String())); + LLSD required_args = llsd::map("name", LLSD(), "label", LLSD(), "reply", LLSD()); add("addMenu", "Add new drop-down menu [\"name\"] with displayed [\"label\"] to the Top menu.", @@ -174,7 +179,7 @@ void LLUIListener::callables(const LLSD& event) const response["callables"] = list; } -void LLUIListener::getValue(const LLSD&event) const +void LLUIListener::getValue(const LLSD& event) const { Response response(LLSD(), event); @@ -192,6 +197,14 @@ void LLUIListener::getValue(const LLSD&event) const } } +void LLUIListener::getParents(const LLSD& event) const +{ + Response response(LLSD(), event); + response["parents"] = llsd::toArray( + *gMenuBarView->getChildList(), + [](auto childp) {return childp->getName(); }); +} + LLMenuGL::Params get_params(const LLSD&event) { LLMenuGL::Params item_params; diff --git a/indra/newview/lluilistener.h b/indra/newview/lluilistener.h index c90253ab59..671eb5f29b 100644 --- a/indra/newview/lluilistener.h +++ b/indra/newview/lluilistener.h @@ -42,7 +42,8 @@ public: private: void call(const LLSD& event); void callables(const LLSD& event) const; - void getValue(const LLSD&event) const; + void getValue(const LLSD& event) const; + void getParents(const LLSD& event) const; void addMenu(const LLSD&event) const; void addMenuBranch(const LLSD&event) const; -- cgit v1.2.3 From 99307b619a5aa27bef3bb67027c4cb5e54f21ad4 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 2 Aug 2024 19:46:52 +0300 Subject: Lua api for adjusting toolbars --- indra/llui/llcommandmanager.cpp | 10 +++++ indra/llui/llcommandmanager.h | 2 + indra/newview/lluilistener.cpp | 68 ++++++++++++++++++++++++++++++++ indra/newview/lluilistener.h | 6 +++ indra/newview/scripts/lua/require/UI.lua | 30 ++++++++++++++ 5 files changed, 116 insertions(+) (limited to 'indra') diff --git a/indra/llui/llcommandmanager.cpp b/indra/llui/llcommandmanager.cpp index 270ec86e01..0c27e519dd 100644 --- a/indra/llui/llcommandmanager.cpp +++ b/indra/llui/llcommandmanager.cpp @@ -189,3 +189,13 @@ bool LLCommandManager::load() return true; } + +LLSD LLCommandManager::getCommandNames() +{ + LLSD cmd_names; + for (auto &it : mCommands) + { + cmd_names.append(it->name()); + } + return cmd_names; + } diff --git a/indra/llui/llcommandmanager.h b/indra/llui/llcommandmanager.h index 3b2586a5a1..cf2270e658 100644 --- a/indra/llui/llcommandmanager.h +++ b/indra/llui/llcommandmanager.h @@ -192,6 +192,8 @@ public: LLCommand * getCommand(const LLCommandId& commandId); LLCommand * getCommand(const std::string& name); + LLSD getCommandNames(); + static bool load(); protected: diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp index b81859a764..6d1d5ac735 100644 --- a/indra/newview/lluilistener.cpp +++ b/indra/newview/lluilistener.cpp @@ -35,6 +35,7 @@ // external library headers // other Linden headers #include "llmenugl.h" +#include "lltoolbarview.h" #include "llui.h" // getRootView(), resolvePath() #include "lluictrl.h" #include "llerror.h" @@ -97,6 +98,32 @@ LLUIListener::LLUIListener(): "Add menu separator to the [\"parent_menu\"] within the Top menu.", &LLUIListener::addMenuSeparator, llsd::map("parent_menu", LLSD(), "reply", LLSD())); + + add("defaultToolbars", + "todo: defaultToolbars desc", + &LLUIListener::restoreDefaultToolbars); + + add("clearToolbars", + "Clear all buttons off the toolbars", + &LLUIListener::clearAllToolbars); + + add("addToolbarBtn", + "Add [\"btn_name\"] toolbar button to the [\"toolbar\"]\n" + "[1 (left toolbar), 2 (right toolbar), 3 (bottom toolbar)]\n" + "Position of the command in the original list can be specified as [\"rank\"]", + &LLUIListener::addToolbarBtn, + llsd::map("btn_name", LLSD(), "reply", LLSD())); + + add("removeToolbarBtn", + "Remove [\"btn_name\"] toolbar button off the toolbar,\n" + "return [\"rank\"] (old position) of the command in the original list", + &LLUIListener::removeToolbarBtn, + llsd::map("btn_name", LLSD(), "reply", LLSD())); + + add("getToolbarBtnNames", + "Return the table of Toolbar buttons names", + &LLUIListener::getToolbarBtnNames, + llsd::map("reply", LLSD())); } typedef LLUICtrl::CommitCallbackInfo cb_info; @@ -280,3 +307,44 @@ void LLUIListener::addMenuSeparator(const LLSD&event) const } } } + +void LLUIListener::restoreDefaultToolbars(const LLSD &event) const +{ + LLToolBarView::loadDefaultToolbars(); +} + +void LLUIListener::clearAllToolbars(const LLSD &event) const +{ + LLToolBarView::clearAllToolbars(); +} + +void LLUIListener::addToolbarBtn(const LLSD &event) const +{ + Response response(LLSD(), event); + + typedef LLToolBarEnums::EToolBarLocation ToolBarLocation; + ToolBarLocation toolbar = ToolBarLocation::TOOLBAR_BOTTOM; + if (event.has("toolbar")) + { + toolbar = llclamp((ToolBarLocation)event["toolbar"].asInteger(), ToolBarLocation::TOOLBAR_NONE, ToolBarLocation::TOOLBAR_BOTTOM); + } + S32 rank = event.has("rank") ? event["rank"].asInteger() : - 1; + if(!gToolBarView->addCommand(event["btn_name"].asString(), toolbar, rank)) + { + response.error(stringize("Toolbar button ", std::quoted(event["btn_name"].asString()), " was not found")); + } +} + +void LLUIListener::removeToolbarBtn(const LLSD &event) const +{ + Response response(LLSD(), event); + + S32 old_rank = LLToolBar::RANK_NONE; + gToolBarView->removeCommand(event["btn_name"].asString(), old_rank); + response["rank"] = old_rank; +} + +void LLUIListener::getToolbarBtnNames(const LLSD &event) const +{ + Response response(llsd::map("cmd_names", LLCommandManager::instance().getCommandNames()), event); +} diff --git a/indra/newview/lluilistener.h b/indra/newview/lluilistener.h index 671eb5f29b..f4bee00807 100644 --- a/indra/newview/lluilistener.h +++ b/indra/newview/lluilistener.h @@ -50,6 +50,12 @@ private: void addMenuItem(const LLSD&event) const; void addMenuSeparator(const LLSD&event) const; + void restoreDefaultToolbars(const LLSD &event) const; + void clearAllToolbars(const LLSD &event) const; + void addToolbarBtn(const LLSD &event) const; + void removeToolbarBtn(const LLSD &event) const; + void getToolbarBtnNames(const LLSD &event) const; + F64 mLastUntrustedThrottle {0}; F64 mLastMinThrottle {0}; }; diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua index 06b49c6269..30b7189c3c 100644 --- a/indra/newview/scripts/lua/require/UI.lua +++ b/indra/newview/scripts/lua/require/UI.lua @@ -169,4 +169,34 @@ function UI.addMenuSeparator(...) return leap.request('UI', args) end +-- *************************************************************************** +-- Toolbar buttons +-- *************************************************************************** +-- Clears all buttons off the toolbars +function UI.clearToolbars() + leap.send('UI', {op='clearToolbars'}) +end + +function UI.defaultToolbars() + leap.send('UI', {op='defaultToolbars'}) +end + +-- UI.addToolbarBtn{btn_name=btn_name +-- [, toolbar= 3] -- 1 [TOOLBAR_LEFT], 2 [TOOLBAR_RIGHT], 3 [TOOLBAR_BOTTOM] +-- [, rank=1]} -- position on the toolbar +function UI.addToolbarBtn(...) + local args = mapargs('btn_name,toolbar,rank', ...) + args.op = 'addToolbarBtn' + return leap.request('UI', args) +end + +-- Returns the rank(position) of the command in the original list +function UI.removeToolbarBtn(btn_name) + return leap.request('UI', {op = 'removeToolbarBtn', btn_name=btn_name}).rank +end + +function UI.getToolbarBtnNames() + return leap.request('UI', {op = 'getToolbarBtnNames'}).cmd_names +end + return UI -- cgit v1.2.3 From a67a01240cbd58a1800290294be83e7b874fefb6 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 2 Aug 2024 20:19:53 +0300 Subject: Lua api for showing/hiding floater; rename demo scripts --- indra/newview/lluilistener.cpp | 10 ++++++ indra/newview/lluilistener.h | 2 ++ indra/newview/scripts/lua/require/UI.lua | 23 +++++++++++++ indra/newview/scripts/lua/test_luafloater_demo.lua | 39 ++++++++++++++++++++++ .../newview/scripts/lua/test_luafloater_demo2.lua | 39 ---------------------- .../scripts/lua/test_luafloater_gesture_list.lua | 27 +++++++++++++++ .../scripts/lua/test_luafloater_gesture_list2.lua | 27 --------------- 7 files changed, 101 insertions(+), 66 deletions(-) create mode 100644 indra/newview/scripts/lua/test_luafloater_demo.lua delete mode 100644 indra/newview/scripts/lua/test_luafloater_demo2.lua create mode 100644 indra/newview/scripts/lua/test_luafloater_gesture_list.lua delete mode 100644 indra/newview/scripts/lua/test_luafloater_gesture_list2.lua (limited to 'indra') diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp index 6d1d5ac735..07a8b45f89 100644 --- a/indra/newview/lluilistener.cpp +++ b/indra/newview/lluilistener.cpp @@ -39,6 +39,7 @@ #include "llui.h" // getRootView(), resolvePath() #include "lluictrl.h" #include "llerror.h" +#include "llviewermenufile.h" // close_all_windows() extern LLMenuBarGL* gMenuBarView; @@ -124,6 +125,10 @@ LLUIListener::LLUIListener(): "Return the table of Toolbar buttons names", &LLUIListener::getToolbarBtnNames, llsd::map("reply", LLSD())); + + add("closeAllFloaters", + "Close all the floaters", + &LLUIListener::closeAllFloaters); } typedef LLUICtrl::CommitCallbackInfo cb_info; @@ -348,3 +353,8 @@ void LLUIListener::getToolbarBtnNames(const LLSD &event) const { Response response(llsd::map("cmd_names", LLCommandManager::instance().getCommandNames()), event); } + +void LLUIListener::closeAllFloaters(const LLSD &event) const +{ + close_all_windows(); +} diff --git a/indra/newview/lluilistener.h b/indra/newview/lluilistener.h index f4bee00807..bae6724b3d 100644 --- a/indra/newview/lluilistener.h +++ b/indra/newview/lluilistener.h @@ -56,6 +56,8 @@ private: void removeToolbarBtn(const LLSD &event) const; void getToolbarBtnNames(const LLSD &event) const; + void closeAllFloaters(const LLSD &event) const; + F64 mLastUntrustedThrottle {0}; F64 mLastMinThrottle {0}; }; diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua index 30b7189c3c..df77eb2b56 100644 --- a/indra/newview/scripts/lua/require/UI.lua +++ b/indra/newview/scripts/lua/require/UI.lua @@ -199,4 +199,27 @@ function UI.getToolbarBtnNames() return leap.request('UI', {op = 'getToolbarBtnNames'}).cmd_names end +-- *************************************************************************** +-- Floaters +-- *************************************************************************** +function UI.showFloater(floater_name) + leap.send("LLFloaterReg", {op = "showInstance", name = floater_name}) +end + +function UI.hideFloater(floater_name) + leap.send("LLFloaterReg", {op = "hideInstance", name = floater_name}) +end + +function UI.toggleFloater(floater_name) + leap.send("LLFloaterReg", {op = "toggleInstance", name = floater_name}) +end + +function UI.isFloaterVisible(floater_name) + return leap.request("LLFloaterReg", {op = "instanceVisible", name = floater_name}).visible +end + +function UI.closeAllFloaters() + return leap.send("UI", {op = "closeAllFloaters"}) +end + return UI diff --git a/indra/newview/scripts/lua/test_luafloater_demo.lua b/indra/newview/scripts/lua/test_luafloater_demo.lua new file mode 100644 index 0000000000..3903d01e65 --- /dev/null +++ b/indra/newview/scripts/lua/test_luafloater_demo.lua @@ -0,0 +1,39 @@ +local Floater = require 'Floater' +local leap = require 'leap' +local startup = require 'startup' + +local flt = Floater( + 'luafloater_demo.xml', + {show_time_lbl = {"right_mouse_down", "double_click"}}) + +-- override base-class handleEvents() to report the event data in the floater's display field +function flt:handleEvents(event_data) + self:post({action="add_text", ctrl_name="events_editor", value = event_data}) + -- forward the call to base-class handleEvents() + return Floater.handleEvents(self, event_data) +end + +function flt:commit_disable_ctrl(event_data) + self:post({action="set_enabled", ctrl_name="open_btn", value = (1 - event_data.value)}) +end + +function flt:commit_title_cmb(event_data) + self:post({action="set_title", value=event_data.value}) +end + +function flt:commit_open_btn(event_data) + floater_name = self:request({action="get_value", ctrl_name='openfloater_cmd'}).value + leap.send("LLFloaterReg", {name = floater_name, op = "showInstance"}) +end + +local function getCurrentTime() + local currentTime = os.date("*t") + return string.format("%02d:%02d:%02d", currentTime.hour, currentTime.min, currentTime.sec) +end + +function flt:double_click_show_time_lbl(event_data) + self:post({action="set_value", ctrl_name="time_lbl", value=getCurrentTime()}) +end + +startup.wait('STATE_LOGIN_WAIT') +flt:show() diff --git a/indra/newview/scripts/lua/test_luafloater_demo2.lua b/indra/newview/scripts/lua/test_luafloater_demo2.lua deleted file mode 100644 index 3903d01e65..0000000000 --- a/indra/newview/scripts/lua/test_luafloater_demo2.lua +++ /dev/null @@ -1,39 +0,0 @@ -local Floater = require 'Floater' -local leap = require 'leap' -local startup = require 'startup' - -local flt = Floater( - 'luafloater_demo.xml', - {show_time_lbl = {"right_mouse_down", "double_click"}}) - --- override base-class handleEvents() to report the event data in the floater's display field -function flt:handleEvents(event_data) - self:post({action="add_text", ctrl_name="events_editor", value = event_data}) - -- forward the call to base-class handleEvents() - return Floater.handleEvents(self, event_data) -end - -function flt:commit_disable_ctrl(event_data) - self:post({action="set_enabled", ctrl_name="open_btn", value = (1 - event_data.value)}) -end - -function flt:commit_title_cmb(event_data) - self:post({action="set_title", value=event_data.value}) -end - -function flt:commit_open_btn(event_data) - floater_name = self:request({action="get_value", ctrl_name='openfloater_cmd'}).value - leap.send("LLFloaterReg", {name = floater_name, op = "showInstance"}) -end - -local function getCurrentTime() - local currentTime = os.date("*t") - return string.format("%02d:%02d:%02d", currentTime.hour, currentTime.min, currentTime.sec) -end - -function flt:double_click_show_time_lbl(event_data) - self:post({action="set_value", ctrl_name="time_lbl", value=getCurrentTime()}) -end - -startup.wait('STATE_LOGIN_WAIT') -flt:show() diff --git a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua new file mode 100644 index 0000000000..bd397ef2a6 --- /dev/null +++ b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua @@ -0,0 +1,27 @@ +local Floater = require 'Floater' +local LLGesture = require 'LLGesture' +local startup = require 'startup' + +local flt = Floater( + "luafloater_gesture_list.xml", + {gesture_list = {"double_click"}}) + +function flt:post_build(event_data) + local gestures_uuid = LLGesture.getActiveGestures() + local action_data = {} + action_data.action = "add_list_element" + action_data.ctrl_name = "gesture_list" + local gestures = {} + for uuid, info in pairs(gestures_uuid) do + table.insert(gestures, {value = uuid, columns={column = "gesture_name", value = info.name}}) + end + action_data.value = gestures + self:post(action_data) +end + +function flt:double_click_gesture_list(event_data) + LLGesture.startGesture(event_data.value) +end + +startup.wait('STATE_STARTED') +flt:show() diff --git a/indra/newview/scripts/lua/test_luafloater_gesture_list2.lua b/indra/newview/scripts/lua/test_luafloater_gesture_list2.lua deleted file mode 100644 index bd397ef2a6..0000000000 --- a/indra/newview/scripts/lua/test_luafloater_gesture_list2.lua +++ /dev/null @@ -1,27 +0,0 @@ -local Floater = require 'Floater' -local LLGesture = require 'LLGesture' -local startup = require 'startup' - -local flt = Floater( - "luafloater_gesture_list.xml", - {gesture_list = {"double_click"}}) - -function flt:post_build(event_data) - local gestures_uuid = LLGesture.getActiveGestures() - local action_data = {} - action_data.action = "add_list_element" - action_data.ctrl_name = "gesture_list" - local gestures = {} - for uuid, info in pairs(gestures_uuid) do - table.insert(gestures, {value = uuid, columns={column = "gesture_name", value = info.name}}) - end - action_data.value = gestures - self:post(action_data) -end - -function flt:double_click_gesture_list(event_data) - LLGesture.startGesture(event_data.value) -end - -startup.wait('STATE_STARTED') -flt:show() -- cgit v1.2.3 From 1913ea4ec2f46458e2971e6e02afacf4da670af2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 2 Aug 2024 19:07:02 -0400 Subject: Search --luafile script on LuaCommandPath. --- indra/newview/llappviewer.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index ef12fe0bd3..ff29f64aeb 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -113,6 +113,7 @@ #include "llgltfmateriallist.h" // Linden library includes +#include "fsyspath.h" #include "llavatarnamecache.h" #include "lldiriterator.h" #include "llexperiencecache.h" @@ -1216,8 +1217,23 @@ bool LLAppViewer::init() "--luafile", "LuaScript", [](const LLSD& script) { - // no completion callback: we don't need to know - LLLUAmanager::runScriptFile(script); + LLSD paths(gSavedSettings.getLLSD("LuaCommandPath")); + LL_DEBUGS("Lua") << "LuaCommandPath = " << paths << LL_ENDL; + for (const auto& path : llsd::inArray(paths)) + { + // if script path is already absolute, operator/() preserves it + auto abspath(fsyspath(gDirUtilp->getAppRODataDir()) / path.asString()); + auto absscript{ (abspath / script.asString()) }; + std::error_code ec; + if (std::filesystem::exists(absscript, ec)) + { + // no completion callback: we don't need to know + LLLUAmanager::runScriptFile(absscript.u8string()); + return; // from lambda + } + } + LL_WARNS("Lua") << "--luafile " << std::quoted(script.asString()) + << " not found on " << paths << LL_ENDL; }); processComposeSwitch( "LuaAutorunPath", "LuaAutorunPath", -- cgit v1.2.3 From af947de6928daa70098edb8346effb9dc8146f47 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 2 Aug 2024 19:24:07 -0400 Subject: Add 'LLPanelLogin' 'login', 'savedLogins' operations. 'login' accepts optional 'username', 'slurl', 'grid'. 'savedLogins' returns the list of saved usernames in both display form and internal form. Make LLPanelLogin::getUserName() accept (const LLPointer&). There's a whole separate discussion pending as to whether const LLPointer should provide access to non-const T methods. Similarly, make LLCredential::getIdentifier() a const method. These two changes enable read-only access to credentials. Make LLPanelLogin methods capture and reuse LLGridManager::instance() as appropriate. Add require/login.lua and test_login.lua. --- indra/newview/llpanellogin.cpp | 57 +++++---- indra/newview/llpanellogin.h | 2 +- indra/newview/llpanelloginlistener.cpp | 178 ++++++++++++++++++++++++++++ indra/newview/llpanelloginlistener.h | 2 + indra/newview/llsecapi.h | 2 +- indra/newview/scripts/lua/require/login.lua | 21 ++-- indra/newview/scripts/lua/test_login.lua | 10 +- 7 files changed, 226 insertions(+), 46 deletions(-) (limited to 'indra') diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index 86f56f0949..a6dbcedc4b 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -81,15 +81,16 @@ BOOL LLPanelLogin::sCredentialSet = FALSE; LLPointer load_user_credentials(std::string &user_key) { - if (gSecAPIHandler->hasCredentialMap("login_list", LLGridManager::getInstance()->getGrid())) + std::string grid{ LLGridManager::instance().getGrid() }; + if (gSecAPIHandler->hasCredentialMap("login_list", grid)) { // user_key should be of "name Resident" format - return gSecAPIHandler->loadFromCredentialMap("login_list", LLGridManager::getInstance()->getGrid(), user_key); + return gSecAPIHandler->loadFromCredentialMap("login_list", grid, user_key); } else { // legacy (or legacy^2, since it also tries to load from settings) - return gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); + return gSecAPIHandler->loadCredential(grid); } } @@ -223,7 +224,8 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, sendChildToBack(getChildView("forgot_password_text")); sendChildToBack(getChildView("sign_up_text")); - std::string current_grid = LLGridManager::getInstance()->getGrid(); + LLGridManager& gridmgr{ LLGridManager::instance() }; + std::string current_grid = gridmgr.getGrid(); if (!mFirstLoginThisInstall) { LLComboBox* favorites_combo = getChild("start_location_combo"); @@ -237,7 +239,7 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, // Load all of the grids, sorted, and then add a bar and the current grid at the top server_choice_combo->removeall(); - std::map known_grids = LLGridManager::getInstance()->getKnownGrids(); + std::map known_grids = gridmgr.getKnownGrids(); for (std::map::iterator grid_choice = known_grids.begin(); grid_choice != known_grids.end(); grid_choice++) @@ -251,7 +253,7 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, server_choice_combo->sortByName(); LL_DEBUGS("AppInit") << "adding current " << current_grid << LL_ENDL; - server_choice_combo->add(LLGridManager::getInstance()->getGridLabel(), + server_choice_combo->add(gridmgr.getGridLabel(), current_grid, ADD_TOP); server_choice_combo->selectFirstItem(); @@ -760,6 +762,7 @@ void LLPanelLogin::onUpdateStartSLURL(const LLSLURL& new_start_slurl) LL_DEBUGS("AppInit")<getChild("start_location_combo"); + LLGridManager& gridmgr{ LLGridManager::instance() }; /* * Determine whether or not the new_start_slurl modifies the grid. * @@ -774,17 +777,17 @@ void LLPanelLogin::onUpdateStartSLURL(const LLSLURL& new_start_slurl) { case LLSLURL::LOCATION: { - std::string slurl_grid = LLGridManager::getInstance()->getGrid(new_start_slurl.getGrid()); + std::string slurl_grid = gridmgr.getGrid(new_start_slurl.getGrid()); if ( ! slurl_grid.empty() ) // is that a valid grid? { - if ( slurl_grid != LLGridManager::getInstance()->getGrid() ) // new grid? + if ( slurl_grid != gridmgr.getGrid() ) // new grid? { // the slurl changes the grid, so update everything to match - LLGridManager::getInstance()->setGridChoice(slurl_grid); + gridmgr.setGridChoice(slurl_grid); // update the grid selector to match the slurl LLComboBox* server_combo = sInstance->getChild("server_combo"); - std::string server_label(LLGridManager::getInstance()->getGridLabel(slurl_grid)); + std::string server_label(gridmgr.getGridLabel(slurl_grid)); server_combo->setSimple(server_label); updateServer(); // to change the links and splash screen @@ -831,8 +834,7 @@ void LLPanelLogin::autologinToLocation(const LLSLURL& slurl) if ( LLPanelLogin::sInstance != NULL ) { - void* unused_parameter = 0; - LLPanelLogin::sInstance->onClickConnect(unused_parameter); + LLPanelLogin::sInstance->onClickConnect(false); } } @@ -872,7 +874,8 @@ void LLPanelLogin::loadLoginPage() { if (!sInstance) return; - LLURI login_page = LLURI(LLGridManager::getInstance()->getLoginPage()); + LLGridManager& gridmgr{ LLGridManager::instance() }; + LLURI login_page = LLURI(gridmgr.getLoginPage()); LLSD params(login_page.queryMap()); LL_DEBUGS("AppInit") << "login_page: " << login_page << LL_ENDL; @@ -899,7 +902,7 @@ void LLPanelLogin::loadLoginPage() params["channel"] = LLVersionInfo::instance().getChannel(); // Grid - params["grid"] = LLGridManager::getInstance()->getGridId(); + params["grid"] = gridmgr.getGridId(); // add OS info params["os"] = LLOSInfo::instance().getOSStringSimple(); @@ -916,7 +919,7 @@ void LLPanelLogin::loadLoginPage() login_page.path(), params)); - gViewerWindow->setMenuBackgroundColor(false, !LLGridManager::getInstance()->isInProductionGrid()); + gViewerWindow->setMenuBackgroundColor(false, !gridmgr.isInProductionGrid()); LLMediaCtrl* web_browser = sInstance->getChild("login_html"); if (web_browser->getCurrentNavUrl() != login_uri.asString()) @@ -949,9 +952,10 @@ void LLPanelLogin::onClickConnect(bool commit_fields) // the grid definitions may come from a user-supplied grids.xml, so they may not be good LL_DEBUGS("AppInit")<<"grid "<setGridChoice(combo_val.asString()); + gridmgr.setGridChoice(combo_val.asString()); } catch (LLInvalidGridName ex) { @@ -984,7 +988,7 @@ void LLPanelLogin::onClickConnect(bool commit_fields) std::string identifier_type; cred->identifierType(identifier_type); LLSD allowed_credential_types; - LLGridManager::getInstance()->getLoginIdentifierTypes(allowed_credential_types); + gridmgr.getLoginIdentifierTypes(allowed_credential_types); // check the typed in credential type against the credential types expected by the server. for(LLSD::array_iterator i = allowed_credential_types.beginArray(); @@ -1133,6 +1137,7 @@ void LLPanelLogin::updateServer() { if (sInstance) { + LLGridManager& gridmgr{ LLGridManager::instance() }; try { // if they've selected another grid, we should load the credentials @@ -1152,7 +1157,7 @@ void LLPanelLogin::updateServer() // populate dropbox and setFields // Note: following call is related to initializeLoginInfo() - LLPointer credential = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); + LLPointer credential = gSecAPIHandler->loadCredential(gridmgr.getGrid()); sInstance->populateUserList(credential); // restore creds @@ -1165,12 +1170,12 @@ void LLPanelLogin::updateServer() { // populate dropbox and setFields // Note: following call is related to initializeLoginInfo() - LLPointer credential = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); + LLPointer credential = gSecAPIHandler->loadCredential(gridmgr.getGrid()); sInstance->populateUserList(credential); } // update the login panel links - bool system_grid = LLGridManager::getInstance()->isSystemGrid(); + bool system_grid = gridmgr.isSystemGrid(); // Want to vanish not only create_new_account_btn, but also the // title text over it, so turn on/off the whole layout_panel element. @@ -1219,11 +1224,12 @@ void LLPanelLogin::populateUserList(LLPointer credential) getChild("password_edit")->setValue(std::string()); mUsernameLength = 0; mPasswordLength = 0; + std::string grid{ LLGridManager::instance().getGrid() }; - if (gSecAPIHandler->hasCredentialMap("login_list", LLGridManager::getInstance()->getGrid())) + if (gSecAPIHandler->hasCredentialMap("login_list", grid)) { LLSecAPIHandler::credential_map_t credencials; - gSecAPIHandler->loadCredentialMap("login_list", LLGridManager::getInstance()->getGrid(), credencials); + gSecAPIHandler->loadCredentialMap("login_list", grid, credencials); LLSecAPIHandler::credential_map_t::iterator cr_iter = credencials.begin(); LLSecAPIHandler::credential_map_t::iterator cr_end = credencials.end(); @@ -1278,7 +1284,8 @@ void LLPanelLogin::onSelectServer() LLComboBox* server_combo = getChild("server_combo"); LLSD server_combo_val = server_combo->getSelectedValue(); LL_INFOS("AppInit") << "grid "<setGridChoice(server_combo_val.asString()); + auto& gridmgr{ LLGridManager::instance() }; + gridmgr.setGridChoice(server_combo_val.asString()); addFavoritesToStartLocation(); /* @@ -1308,7 +1315,7 @@ void LLPanelLogin::onSelectServer() LLSLURL slurl(location); // generata a slurl from the location combo contents if (location.empty() || (slurl.getType() == LLSLURL::LOCATION - && slurl.getGrid() != LLGridManager::getInstance()->getGrid()) + && slurl.getGrid() != gridmgr.getGrid()) ) { // the grid specified by the location is not this one, so clear the combo @@ -1338,7 +1345,7 @@ bool LLPanelLogin::getShowFavorites() } // static -std::string LLPanelLogin::getUserName(LLPointer &cred) +std::string LLPanelLogin::getUserName(const LLPointer &cred) { if (cred.isNull()) { diff --git a/indra/newview/llpanellogin.h b/indra/newview/llpanellogin.h index 7f4c2487bf..a7019ea2a3 100644 --- a/indra/newview/llpanellogin.h +++ b/indra/newview/llpanellogin.h @@ -88,7 +88,7 @@ public: static bool getShowFavorites(); // extract name from cred in a format apropriate for username field - static std::string getUserName(LLPointer &cred); + static std::string getUserName(const LLPointer &cred); private: friend class LLPanelLoginListener; diff --git a/indra/newview/llpanelloginlistener.cpp b/indra/newview/llpanelloginlistener.cpp index fb6f034b7f..a4418145d7 100644 --- a/indra/newview/llpanelloginlistener.cpp +++ b/indra/newview/llpanelloginlistener.cpp @@ -32,9 +32,19 @@ #include "llpanelloginlistener.h" // STL headers // std headers +#include +#include // external library headers // other Linden headers +#include "llcombobox.h" #include "llpanellogin.h" +#include "llsdutil.h" +#include "llsecapi.h" +#include "llslurl.h" +#include "llstartup.h" +#include "lluictrl.h" +#include "llviewernetwork.h" +#include "stringize.h" LLPanelLoginListener::LLPanelLoginListener(LLPanelLogin* instance): LLEventAPI("LLPanelLogin", "Access to LLPanelLogin methods"), @@ -43,9 +53,177 @@ LLPanelLoginListener::LLPanelLoginListener(LLPanelLogin* instance): add("onClickConnect", "Pretend user clicked the \"Log In\" button", &LLPanelLoginListener::onClickConnect); + add("login", + "Login to Second Life with saved credentials.\n" + "Pass [\"username\"] to select credentials previously saved with that username.\n" + "Pass [\"slurl\"] to specify target location.\n" + "Pass [\"grid\"] to select one of the valid grids in grids.xml.", + &LLPanelLoginListener::login, + llsd::map("reply", LLSD::String())); + add("savedLogins", + "Return \"logins\" as a list of {} saved login names.\n" + "Pass [\"grid\"] to select one of the valid grids in grids.xml.", + &LLPanelLoginListener::savedLogins, + llsd::map("reply", LLSD::String())); } void LLPanelLoginListener::onClickConnect(const LLSD&) const { mPanel->onClickConnect(false); } + +void LLPanelLoginListener::login(const LLSD& args) const +{ + // Although we require a "reply" key, don't instantiate a Response object: + // if things go well, response to our invoker will come later, once the + // viewer gets the response from the login server. + std::string username(args["username"]), slurlstr(args["slurl"]), grid(args["grid"]); + LL_DEBUGS("AppInit") << "Login with saved credentials"; + if (! username.empty()) + { + LL_CONT << " for user " << std::quoted(username); + } + if (! slurlstr.empty()) + { + LL_CONT << " to location " << std::quoted(slurlstr); + } + if (! grid.empty()) + { + LL_CONT << " on grid " << std::quoted(grid); + } + LL_CONT << LL_ENDL; + + // change grid first, allowing slurl to override + auto& gridmgr{ LLGridManager::instance() }; + if (! grid.empty()) + { + // setGridChoice() can throw LLInvalidGridName -- but if so, let it + // propagate, trusting that LLEventAPI will catch it and send an + // appropriate reply. + auto server_combo = mPanel->getChild("server_combo"); + server_combo->setSimple(gridmgr.getGridLabel(grid)); + LLPanelLogin::updateServer(); + } + if (! slurlstr.empty()) + { + LLSLURL slurl(slurlstr); + if (! slurl.isSpatial()) + { + // don't bother with LLTHROW() because we expect LLEventAPI to + // catch and report back to invoker + throw DispatchError(stringize("Invalid start SLURL ", std::quoted(slurlstr))); + } + // Bypass LLStartUp::setStartSLURL() because, after validating as + // above, it bounces right back to LLPanelLogin::onUpdateStartSLURL(). + // It also sets the "NextLoginLocation" user setting, but as with grid, + // we don't yet know whether that's desirable for scripted login. + LLPanelLogin::onUpdateStartSLURL(slurl); + } + if (! username.empty()) + { + // Transform (e.g.) "Nat Linden" to the internal form expected by + // loadFromCredentialMap(), e.g. "nat_linden" + LLStringUtil::toLower(username); + LLStringUtil::replaceChar(username, ' ', '_'); + LLStringUtil::replaceChar(username, '.', '_'); + + // call gridmgr.getGrid() because our caller didn't necessarily pass + // ["grid"] -- or it might have been overridden by the SLURL + auto cred = gSecAPIHandler->loadFromCredentialMap("login_list", + gridmgr.getGrid(), + username); + LLPanelLogin::setFields(cred); + } + + if (mPanel->getChild("username_combo")->getValue().asString().empty() || + mPanel->getChild("password_edit") ->getValue().asString().empty()) + { + // as above, let LLEventAPI report back to invoker + throw DispatchError(stringize("Unrecognized username ", std::quoted(username))); + } + + // Here we're about to trigger actual login, which is all we can do in + // this method. All subsequent responses must come via the "login" + // LLEventPump. + LLEventPumps::instance().obtain("login").listen( + "Lua login", + [args] + (const LLBoundListener& conn, const LLSD& update) + { + LLSD response{ llsd::map("ok", false) }; + if (update["change"] == "connect") + { + // success! + response["ok"] = true; + } + else if (update["change"] == "disconnect") + { + // Does this ever actually happen? + // LLLoginInstance::handleDisconnect() says it's a placeholder. + response["error"] = "login disconnect"; + } + else if (update["change"] == "fail.login") + { + // why? + // LLLoginInstance::handleLoginFailure() has special code to + // handle "tos", "update" and "mfa_challenge". Do not respond + // automatically because these things are SUPPOSED to engage + // the user. + // Copy specific response fields because there's an enormous + // chunk of stuff that comes back on success. + LLSD data{ update["data"] }; + const char* fields[] = { + "reason", + "error_code" + }; + for (auto field : fields) + { + response[field] = data[field]; + } + response["error"] = update["message"]; + } + else + { + // Ignore all other "change" values: LLLogin sends multiple update + // events. Only a few of them (above) indicate completion. + return; + } + // For all the completion cases, disconnect from the "login" + // LLEventPump. + conn.disconnect(); + // and at last, send response to the original invoker + sendReply(response, args); + }); + + // Turn the Login crank. + mPanel->onClickConnect(false); +} + +LLSD LLPanelLoginListener::savedLogins(const LLSD& args) const +{ + LL_INFOS() << "called with " << args << LL_ENDL; + std::string grid = (args.has("grid")? args["grid"].asString() + : LLGridManager::instance().getGrid()); + if (! gSecAPIHandler->hasCredentialMap("login_list", grid)) + { + LL_INFOS() << "no creds for " << grid << LL_ENDL; + return llsd::map("error", + stringize("No saved logins for grid ", std::quoted(grid))); + } + LLSecAPIHandler::credential_map_t creds; + gSecAPIHandler->loadCredentialMap("login.list", grid, creds); + auto result = llsd::map( + "logins", + llsd::toArray( + creds, + [](const auto& pair) + { + return llsd::map( + "name", + LLPanelLogin::getUserName(pair.second), + "key", + pair.first); + })); + LL_INFOS() << "returning creds " << result << LL_ENDL; + return result; +} diff --git a/indra/newview/llpanelloginlistener.h b/indra/newview/llpanelloginlistener.h index b552ccd5b0..f2f4d70ff9 100644 --- a/indra/newview/llpanelloginlistener.h +++ b/indra/newview/llpanelloginlistener.h @@ -40,6 +40,8 @@ public: private: void onClickConnect(const LLSD&) const; + void login(const LLSD&) const; + LLSD savedLogins(const LLSD&) const; LLPanelLogin* mPanel; }; diff --git a/indra/newview/llsecapi.h b/indra/newview/llsecapi.h index 5cc78d09dc..367bfe1469 100644 --- a/indra/newview/llsecapi.h +++ b/indra/newview/llsecapi.h @@ -311,7 +311,7 @@ public: mIdentifier = identifier; mAuthenticator = authenticator; } - virtual LLSD getIdentifier() { return mIdentifier; } + virtual LLSD getIdentifier() const { return mIdentifier; } virtual void identifierType(std::string& idType); virtual LLSD getAuthenticator() { return mAuthenticator; } virtual void authenticatorType(std::string& authType); diff --git a/indra/newview/scripts/lua/require/login.lua b/indra/newview/scripts/lua/require/login.lua index 0d8591cace..f4e65b1606 100644 --- a/indra/newview/scripts/lua/require/login.lua +++ b/indra/newview/scripts/lua/require/login.lua @@ -1,19 +1,12 @@ -local UI = require 'UI' local leap = require 'leap' +local startup = require 'startup' +local mapargs = require 'mapargs' -local function login(username, password) - if username and password then - local userpath = '//username_combo/Combo Text Entry' - local passpath = '//password_edit' - -- first clear anything presently in those text fields - for _, path in pairs({userpath, passpath}) do - UI.click(path) - UI.keypress{keysym='Backsp', path=path} - end - UI.type{path=userpath, text=username} - UI.type{path=passpath, text=password} - end - leap.send('LLPanelLogin', {op='onClickConnect'}) +local function login(...) + startup.wait('STATE_LOGIN_WAIT') + local args = mapargs('username,grid,slurl', ...) + args.op = 'login' + return leap.request('LLPanelLogin', args) end return login diff --git a/indra/newview/scripts/lua/test_login.lua b/indra/newview/scripts/lua/test_login.lua index a8c31807bc..f263940f79 100644 --- a/indra/newview/scripts/lua/test_login.lua +++ b/indra/newview/scripts/lua/test_login.lua @@ -1,7 +1,7 @@ -startup = require 'startup' +inspect = require 'inspect' login = require 'login' -startup.wait('STATE_LOGIN_WAIT') -login() --- Fill in valid credentials as they would be entered on the login screen --- login('My Username', 'password') +print(inspect(login{ + username='Nat Linden', +-- grid='agni' + })) -- cgit v1.2.3 From b12d135c38e327774c01594acfa96e37a9e67a64 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Aug 2024 11:24:01 -0400 Subject: Fix a couple problems with "LLPanelLogin" listener (thanks Maxim!). Convert plain grid (e.g. "agni") to domain form (e.g. "util.agni.lindenlab.com"). Fix a typo in `savedLogins()`: "login_list", not "login.list". login.lua now returns a table with two functions: `login.login()` and `login.savedLogins()`. Defend Lua caller against trying to engage login features too late in startup sequence: in addition to waiting for "STATE_LOGIN_WAIT", produce an error if `startup.state()` is beyond that state. Since by then `LLPanelLogin` is destroyed, `leap.request("LLPanelLogin", ...)` would get no response, causing the calling Lua script to hang until viewer shutdown. --- indra/newview/llpanelloginlistener.cpp | 22 ++++++++++++++++++++-- indra/newview/scripts/lua/require/login.lua | 23 ++++++++++++++++++++++- indra/newview/scripts/lua/test_login.lua | 9 ++++++--- 3 files changed, 48 insertions(+), 6 deletions(-) (limited to 'indra') diff --git a/indra/newview/llpanelloginlistener.cpp b/indra/newview/llpanelloginlistener.cpp index a4418145d7..aeaf2d1d00 100644 --- a/indra/newview/llpanelloginlistener.cpp +++ b/indra/newview/llpanelloginlistener.cpp @@ -72,6 +72,23 @@ void LLPanelLoginListener::onClickConnect(const LLSD&) const mPanel->onClickConnect(false); } +namespace +{ + + std::string gridkey(const std::string& grid) + { + if (grid.find('.') != std::string::npos) + { + return grid; + } + else + { + return stringize("util.", grid, ".lindenlab.com"); + } + } + +} // anonymous namespace + void LLPanelLoginListener::login(const LLSD& args) const { // Although we require a "reply" key, don't instantiate a Response object: @@ -97,6 +114,7 @@ void LLPanelLoginListener::login(const LLSD& args) const auto& gridmgr{ LLGridManager::instance() }; if (! grid.empty()) { + grid = gridkey(grid); // setGridChoice() can throw LLInvalidGridName -- but if so, let it // propagate, trusting that LLEventAPI will catch it and send an // appropriate reply. @@ -202,7 +220,7 @@ void LLPanelLoginListener::login(const LLSD& args) const LLSD LLPanelLoginListener::savedLogins(const LLSD& args) const { LL_INFOS() << "called with " << args << LL_ENDL; - std::string grid = (args.has("grid")? args["grid"].asString() + std::string grid = (args.has("grid")? gridkey(args["grid"].asString()) : LLGridManager::instance().getGrid()); if (! gSecAPIHandler->hasCredentialMap("login_list", grid)) { @@ -211,7 +229,7 @@ LLSD LLPanelLoginListener::savedLogins(const LLSD& args) const stringize("No saved logins for grid ", std::quoted(grid))); } LLSecAPIHandler::credential_map_t creds; - gSecAPIHandler->loadCredentialMap("login.list", grid, creds); + gSecAPIHandler->loadCredentialMap("login_list", grid, creds); auto result = llsd::map( "logins", llsd::toArray( diff --git a/indra/newview/scripts/lua/require/login.lua b/indra/newview/scripts/lua/require/login.lua index f4e65b1606..50fc9e3793 100644 --- a/indra/newview/scripts/lua/require/login.lua +++ b/indra/newview/scripts/lua/require/login.lua @@ -2,11 +2,32 @@ local leap = require 'leap' local startup = require 'startup' local mapargs = require 'mapargs' -local function login(...) +local login = {} + +local function ensure_login_state(op) + -- no point trying to login until the viewer is ready startup.wait('STATE_LOGIN_WAIT') + -- Once we've actually started login, LLPanelLogin is destroyed, and so is + -- its "LLPanelLogin" listener. At that point, + -- leap.request("LLPanelLogin", ...) will hang indefinitely because no one + -- is listening on that LLEventPump any more. Intercept that case and + -- produce a sensible error. + local state = startup.state() + if startup.before('STATE_LOGIN_WAIT', state) then + error(`Can't engage login operation {op} once we've reached state {state}`, 2) + end +end + +function login.login(...) + ensure_login_state('login') local args = mapargs('username,grid,slurl', ...) args.op = 'login' return leap.request('LLPanelLogin', args) end +function login.savedLogins(grid) + ensure_login_state('savedLogins') + return leap.request('LLPanelLogin', {op='savedLogins'})['logins'] +end + return login diff --git a/indra/newview/scripts/lua/test_login.lua b/indra/newview/scripts/lua/test_login.lua index f263940f79..54d3635a41 100644 --- a/indra/newview/scripts/lua/test_login.lua +++ b/indra/newview/scripts/lua/test_login.lua @@ -1,7 +1,10 @@ inspect = require 'inspect' login = require 'login' -print(inspect(login{ - username='Nat Linden', --- grid='agni' +local grid = 'agni' +print(inspect(login.savedLogins(grid))) + +print(inspect(login.login{ + username='Your Username', + grid=grid })) -- cgit v1.2.3 From cf29b701b19644062a1428a64023c3cf9829e2de Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Mon, 5 Aug 2024 20:37:03 +0300 Subject: Allow getting the list of floater names, hide top menu items; add demo script --- indra/llui/llfloaterreg.cpp | 10 +++++++++ indra/llui/llfloaterreg.h | 2 ++ indra/llui/llfloaterreglistener.cpp | 11 +++++++++ indra/llui/llfloaterreglistener.h | 1 + indra/newview/lluilistener.cpp | 16 ++++++++++++++ indra/newview/lluilistener.h | 1 + indra/newview/scripts/lua/require/UI.lua | 8 +++++++ indra/newview/scripts/lua/test_LLChatListener.lua | 19 ++++++++++++---- indra/newview/scripts/lua/test_toolbars.lua | 27 +++++++++++++++++++++++ 9 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 indra/newview/scripts/lua/test_toolbars.lua (limited to 'indra') diff --git a/indra/llui/llfloaterreg.cpp b/indra/llui/llfloaterreg.cpp index 989ce12d09..a9ed678973 100644 --- a/indra/llui/llfloaterreg.cpp +++ b/indra/llui/llfloaterreg.cpp @@ -607,3 +607,13 @@ U32 LLFloaterReg::getVisibleFloaterInstanceCount() return count; } + +LLSD LLFloaterReg::getFloaterNames() +{ + LLSD names; + for (auto &it : sGroupMap) + { + names.append(it.first); + } + return names; +} diff --git a/indra/llui/llfloaterreg.h b/indra/llui/llfloaterreg.h index 43f3f7b170..31a334b89c 100644 --- a/indra/llui/llfloaterreg.h +++ b/indra/llui/llfloaterreg.h @@ -153,6 +153,8 @@ public: static void blockShowFloaters(bool value) { sBlockShowFloaters = value;} static U32 getVisibleFloaterInstanceCount(); + + static LLSD getFloaterNames(); }; #endif diff --git a/indra/llui/llfloaterreglistener.cpp b/indra/llui/llfloaterreglistener.cpp index 8316101264..e17f9f4dd6 100644 --- a/indra/llui/llfloaterreglistener.cpp +++ b/indra/llui/llfloaterreglistener.cpp @@ -80,6 +80,11 @@ LLFloaterRegListener::LLFloaterRegListener(): add("getFloaterEvents", "Return the table of Lua Floater events which are send to the script", &LLFloaterRegListener::getLuaFloaterEvents); + + add("getFloaterNames", + "Return the table of all registered floaters", + &LLFloaterRegListener::getFloaterNames, + llsd::map("reply", LLSD())); } void LLFloaterRegListener::getBuildMap(const LLSD& event) const @@ -121,6 +126,12 @@ void LLFloaterRegListener::instanceVisible(const LLSD& event) const event); } + +void LLFloaterRegListener::getFloaterNames(const LLSD &event) const +{ + Response response(llsd::map("floaters", LLFloaterReg::getFloaterNames()), event); +} + void LLFloaterRegListener::clickButton(const LLSD& event) const { // If the caller requests a reply, build the reply. diff --git a/indra/llui/llfloaterreglistener.h b/indra/llui/llfloaterreglistener.h index 9cb0af2de5..2165b1b62f 100644 --- a/indra/llui/llfloaterreglistener.h +++ b/indra/llui/llfloaterreglistener.h @@ -49,6 +49,7 @@ private: void toggleInstance(const LLSD& event) const; void instanceVisible(const LLSD& event) const; void clickButton(const LLSD& event) const; + void getFloaterNames(const LLSD &event) const; void getLuaFloaterEvents(const LLSD &event) const; }; diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp index 07a8b45f89..bcc13c5fe9 100644 --- a/indra/newview/lluilistener.cpp +++ b/indra/newview/lluilistener.cpp @@ -100,6 +100,11 @@ LLUIListener::LLUIListener(): &LLUIListener::addMenuSeparator, llsd::map("parent_menu", LLSD(), "reply", LLSD())); + add("setMenuVisible", + "Set menu [\"name\"] visibility to [\"visible\"]", + &LLUIListener::setMenuVisible, + llsd::map("name", LLSD(), "visible", LLSD(), "reply", LLSD())); + add("defaultToolbars", "todo: defaultToolbars desc", &LLUIListener::restoreDefaultToolbars); @@ -313,6 +318,17 @@ void LLUIListener::addMenuSeparator(const LLSD&event) const } } +void LLUIListener::setMenuVisible(const LLSD &event) const +{ + Response response(LLSD(), event); + std::string menu_name(event["name"]); + if (!gMenuBarView->getItem(menu_name)) + { + return response.error(stringize("Menu ", std::quoted(menu_name), " was not found")); + } + gMenuBarView->setItemVisible(menu_name, event["visible"].asBoolean()); +} + void LLUIListener::restoreDefaultToolbars(const LLSD &event) const { LLToolBarView::loadDefaultToolbars(); diff --git a/indra/newview/lluilistener.h b/indra/newview/lluilistener.h index bae6724b3d..98e4754306 100644 --- a/indra/newview/lluilistener.h +++ b/indra/newview/lluilistener.h @@ -49,6 +49,7 @@ private: void addMenuBranch(const LLSD&event) const; void addMenuItem(const LLSD&event) const; void addMenuSeparator(const LLSD&event) const; + void setMenuVisible(const LLSD &event) const; void restoreDefaultToolbars(const LLSD &event) const; void clearAllToolbars(const LLSD &event) const; diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua index df77eb2b56..df76b1501c 100644 --- a/indra/newview/scripts/lua/require/UI.lua +++ b/indra/newview/scripts/lua/require/UI.lua @@ -150,6 +150,10 @@ function UI.addMenu(...) return leap.request('UI', args) end +function UI.setMenuVisible(name, visible) + return leap.request('UI', {op='setMenuVisible', name=name, visible=visible}) +end + function UI.addMenuBranch(...) local args = mapargs('name,label,parent_menu', ...) args.op = 'addMenuBranch' @@ -222,4 +226,8 @@ function UI.closeAllFloaters() return leap.send("UI", {op = "closeAllFloaters"}) end +function UI.getFloaterNames() + return leap.request("LLFloaterReg", {op = "getFloaterNames"}).floaters +end + return UI diff --git a/indra/newview/scripts/lua/test_LLChatListener.lua b/indra/newview/scripts/lua/test_LLChatListener.lua index 18363ed43b..4a4d40bee5 100644 --- a/indra/newview/scripts/lua/test_LLChatListener.lua +++ b/indra/newview/scripts/lua/test_LLChatListener.lua @@ -1,11 +1,22 @@ local LLChatListener = require 'LLChatListener' local LLChat = require 'LLChat' -local leap = require 'leap' +local UI = require 'UI' +-- Chat listener script allows to use the following commands in Nearby chat: +-- open inventory -- open defined floater by name +-- close inventory -- close defined floater by name +-- closeall -- close all floaters +-- stop -- close the script +-- any other messages will be echoed. function openOrEcho(message) - local floater_name = string.match(message, "^open%s+(%w+)") - if floater_name then - leap.send("LLFloaterReg", {name = floater_name, op = "showInstance"}) + local open_floater_name = string.match(message, "^open%s+(%w+)") + local close_floater_name = string.match(message, "^close%s+(%w+)") + if open_floater_name then + UI.showFloater(open_floater_name) + elseif close_floater_name then + UI.hideFloater(close_floater_name) + elseif message == 'closeall' then + UI.closeAllFloaters() else LLChat.sendNearby('Echo: ' .. message) end diff --git a/indra/newview/scripts/lua/test_toolbars.lua b/indra/newview/scripts/lua/test_toolbars.lua new file mode 100644 index 0000000000..70035db775 --- /dev/null +++ b/indra/newview/scripts/lua/test_toolbars.lua @@ -0,0 +1,27 @@ +popup = require 'popup' +UI = require 'UI' + +local OK = 'OK_okcancelbuttons' +local BUTTONS = UI.getToolbarBtnNames() + +-- Clear the toolbars and then add the toolbar buttons to the random toolbar +response = popup:alertYesCancel('Toolbars will be randomly reshuffled. Proceed?') +if next(response) == OK then + UI.clearToolbars() + math.randomseed(os.time()) + + -- add the buttons to the random toolbar + for i = 1, #BUTTONS do + UI.addToolbarBtn(BUTTONS[i], math.random(3)) + end + + -- remove some of the added buttons from the toolbars + for i = 1, #BUTTONS do + if math.random(100) < 30 then + UI.removeToolbarBtn(BUTTONS[i]) + end + end + popup:tip('Toolbars were reshuffled') +else + popup:tip('Canceled') +end -- cgit v1.2.3 From eb82c78b071d71a0fd2d7be1c573997e41bab51e Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Tue, 6 Aug 2024 20:38:06 +0300 Subject: code clean up --- indra/llui/llcommandmanager.cpp | 8 ++------ indra/llui/llfloaterreg.cpp | 7 +------ indra/newview/lluilistener.cpp | 26 +++++++++++++++++++------- indra/newview/scripts/lua/require/UI.lua | 8 ++++---- indra/newview/scripts/lua/test_toolbars.lua | 5 +++-- 5 files changed, 29 insertions(+), 25 deletions(-) (limited to 'indra') diff --git a/indra/llui/llcommandmanager.cpp b/indra/llui/llcommandmanager.cpp index 0c27e519dd..6c3e676c9b 100644 --- a/indra/llui/llcommandmanager.cpp +++ b/indra/llui/llcommandmanager.cpp @@ -32,6 +32,7 @@ #include "llcommandmanager.h" #include "lldir.h" #include "llerror.h" +#include "llsdutil.h" #include "llxuiparser.h" @@ -192,10 +193,5 @@ bool LLCommandManager::load() LLSD LLCommandManager::getCommandNames() { - LLSD cmd_names; - for (auto &it : mCommands) - { - cmd_names.append(it->name()); - } - return cmd_names; + return llsd::toArray(mCommands, [](const auto &cmd) { return cmd->name(); }); } diff --git a/indra/llui/llfloaterreg.cpp b/indra/llui/llfloaterreg.cpp index a9ed678973..8f9268ffcb 100644 --- a/indra/llui/llfloaterreg.cpp +++ b/indra/llui/llfloaterreg.cpp @@ -610,10 +610,5 @@ U32 LLFloaterReg::getVisibleFloaterInstanceCount() LLSD LLFloaterReg::getFloaterNames() { - LLSD names; - for (auto &it : sGroupMap) - { - names.append(it.first); - } - return names; + return llsd::toArray(sGroupMap, [](const auto &pair) { return pair.first; }); } diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp index bcc13c5fe9..3e67531388 100644 --- a/indra/newview/lluilistener.cpp +++ b/indra/newview/lluilistener.cpp @@ -106,23 +106,24 @@ LLUIListener::LLUIListener(): llsd::map("name", LLSD(), "visible", LLSD(), "reply", LLSD())); add("defaultToolbars", - "todo: defaultToolbars desc", + "Restore default toolbar buttons", &LLUIListener::restoreDefaultToolbars); - add("clearToolbars", + add("clearAllToolbars", "Clear all buttons off the toolbars", &LLUIListener::clearAllToolbars); add("addToolbarBtn", - "Add [\"btn_name\"] toolbar button to the [\"toolbar\"]\n" - "[1 (left toolbar), 2 (right toolbar), 3 (bottom toolbar)]\n" + "Add [\"btn_name\"] toolbar button to the [\"toolbar\"]:\n" + "\"left\", \"right\", \"bottom\" (default is \"bottom\")\n" "Position of the command in the original list can be specified as [\"rank\"]", &LLUIListener::addToolbarBtn, llsd::map("btn_name", LLSD(), "reply", LLSD())); add("removeToolbarBtn", "Remove [\"btn_name\"] toolbar button off the toolbar,\n" - "return [\"rank\"] (old position) of the command in the original list", + "return [\"rank\"] (old position) of the command in the original list,\n" + "rank -1 means that [\"btn_name\"] was not found", &LLUIListener::removeToolbarBtn, llsd::map("btn_name", LLSD(), "reply", LLSD())); @@ -347,9 +348,20 @@ void LLUIListener::addToolbarBtn(const LLSD &event) const ToolBarLocation toolbar = ToolBarLocation::TOOLBAR_BOTTOM; if (event.has("toolbar")) { - toolbar = llclamp((ToolBarLocation)event["toolbar"].asInteger(), ToolBarLocation::TOOLBAR_NONE, ToolBarLocation::TOOLBAR_BOTTOM); + if (event["toolbar"] == "left") + { + toolbar = ToolBarLocation::TOOLBAR_LEFT; + } + else if (event["toolbar"] == "right") + { + toolbar = ToolBarLocation::TOOLBAR_RIGHT; + } + else if (event["toolbar"] != "bottom") + { + return response.error(stringize("Toolbar name ", std::quoted(event["toolbar"].asString()), " is not correct. Toolbar names are: left, right, bottom")); + } } - S32 rank = event.has("rank") ? event["rank"].asInteger() : - 1; + S32 rank = event.has("rank") ? event["rank"].asInteger() : LLToolBar::RANK_NONE; if(!gToolBarView->addCommand(event["btn_name"].asString(), toolbar, rank)) { response.error(stringize("Toolbar button ", std::quoted(event["btn_name"].asString()), " was not found")); diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua index df76b1501c..9bc9a3685d 100644 --- a/indra/newview/scripts/lua/require/UI.lua +++ b/indra/newview/scripts/lua/require/UI.lua @@ -177,8 +177,8 @@ end -- Toolbar buttons -- *************************************************************************** -- Clears all buttons off the toolbars -function UI.clearToolbars() - leap.send('UI', {op='clearToolbars'}) +function UI.clearAllToolbars() + leap.send('UI', {op='clearAllToolbars'}) end function UI.defaultToolbars() @@ -186,8 +186,8 @@ function UI.defaultToolbars() end -- UI.addToolbarBtn{btn_name=btn_name --- [, toolbar= 3] -- 1 [TOOLBAR_LEFT], 2 [TOOLBAR_RIGHT], 3 [TOOLBAR_BOTTOM] --- [, rank=1]} -- position on the toolbar +-- [, toolbar= bottom] -- left, right, bottom -- default is bottom +-- [, rank=1]} -- position on the toolbar, starts at 0 (0 - first position, 1 - second position etc.) function UI.addToolbarBtn(...) local args = mapargs('btn_name,toolbar,rank', ...) args.op = 'addToolbarBtn' diff --git a/indra/newview/scripts/lua/test_toolbars.lua b/indra/newview/scripts/lua/test_toolbars.lua index 70035db775..9a832c5644 100644 --- a/indra/newview/scripts/lua/test_toolbars.lua +++ b/indra/newview/scripts/lua/test_toolbars.lua @@ -3,16 +3,17 @@ UI = require 'UI' local OK = 'OK_okcancelbuttons' local BUTTONS = UI.getToolbarBtnNames() +local TOOLBARS = {'left','right','bottom'} -- Clear the toolbars and then add the toolbar buttons to the random toolbar response = popup:alertYesCancel('Toolbars will be randomly reshuffled. Proceed?') if next(response) == OK then - UI.clearToolbars() + UI.clearAllToolbars() math.randomseed(os.time()) -- add the buttons to the random toolbar for i = 1, #BUTTONS do - UI.addToolbarBtn(BUTTONS[i], math.random(3)) + UI.addToolbarBtn(BUTTONS[i], TOOLBARS[math.random(3)]) end -- remove some of the added buttons from the toolbars -- cgit v1.2.3 From d3b1859ca3d6c2c2bcc92edba994b522cf9d4e99 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 6 Aug 2024 15:55:58 -0400 Subject: Introduce a custom coroutine/fiber scheduler to prioritize UI. The viewer's main thread's main fiber is responsible for coordinating just about everything. With the default round_robin fiber scheduling algorithm, launching too many additional fibers could starve the main fiber, resulting in visible lag. This custom scheduler tracks when it switches to and from the main fiber, and at each context switch, how long it's been since the last time the main fiber ran. If that exceeds a certain timeslice, it jumps the main fiber to the head of the queue and resumes that instead of any other ready fiber. --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/coro_scheduler.cpp | 164 ++++++++++++++++++++++++++++++++++++++ indra/llcommon/coro_scheduler.h | 73 +++++++++++++++++ indra/newview/llstartup.cpp | 5 +- 4 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 indra/llcommon/coro_scheduler.cpp create mode 100644 indra/llcommon/coro_scheduler.h (limited to 'indra') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 20670d7ebe..221fc2bb3f 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -18,6 +18,7 @@ include(Tracy) set(llcommon_SOURCE_FILES apply.cpp commoncontrol.cpp + coro_scheduler.cpp hbxxh.cpp indra_constants.cpp lazyeventapi.cpp @@ -125,6 +126,7 @@ set(llcommon_HEADER_FILES chrono.h classic_callback.h commoncontrol.h + coro_scheduler.h ctype_workaround.h fix_macros.h fsyspath.h diff --git a/indra/llcommon/coro_scheduler.cpp b/indra/llcommon/coro_scheduler.cpp new file mode 100644 index 0000000000..337162cbd5 --- /dev/null +++ b/indra/llcommon/coro_scheduler.cpp @@ -0,0 +1,164 @@ +/** + * @file coro_scheduler.cpp + * @author Nat Goodspeed + * @date 2024-08-05 + * @brief Implementation for llcoro::scheduler. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "coro_scheduler.h" +// STL headers +// std headers +#include +// external library headers +#include +// other Linden headers +#include "llcallbacklist.h" +#include "lldate.h" +#include "llerror.h" + +namespace llcoro +{ + +const F32 scheduler::DEFAULT_TIMESLICE{ LL::Timers::DEFAULT_TIMESLICE }; + +const std::string qname("General"); + +scheduler::scheduler(): + // Since use_scheduling_algorithm() must be called before any other + // Boost.Fibers operations, we can assume that the calling fiber is in + // fact the main fiber. + mMainID(boost::this_fiber::get_id()), + mStart(LLDate::now().secondsSinceEpoch()), + mQueue(LL::WorkQueue::getInstance(qname)) +{} + +void scheduler::awakened( boost::fibers::context* ctx) noexcept +{ + if (ctx->get_id() == mMainID) + { + // If the fiber that just came ready is the main fiber, record its + // pointer. + llassert(! mMainCtx); + mMainCtx = ctx; + } + // Delegate to round_robin::awakened() as usual, even for the main fiber. + // This way, as long as other fibers don't take too long, we can just let + // normal round_robin processing pass control to the main fiber. + super::awakened(ctx); +} + +boost::fibers::context* scheduler::pick_next() noexcept +{ + // count calls to pick_next() + ++mSwitches; + // pick_next() is called when the previous fiber has suspended, and we + // need to pick another. Did the previous pick_next() call pick the main + // fiber? If so, it's the main fiber that just suspended. + auto now = LLDate::now().secondsSinceEpoch(); + if (mMainRunning) + { + mMainRunning = false; + mMainLast = now; + } + + boost::fibers::context* next; + + // When the main fiber is ready, and it's been more than mTimeslice since + // the main fiber last ran, it's time to intervene. + F32 elapsed(now - mMainLast); + if (mMainCtx && elapsed > mTimeslice) + { + // We claim that the main fiber is not only stored in mMainCtx, but is + // also queued (somewhere) in our ready list. + llassert(mMainCtx->ready_is_linked()); + // The usefulness of a doubly-linked list is that, given only a + // pointer to an item, we can unlink it. + mMainCtx->ready_unlink(); + // Instead of delegating to round_robin::pick_next() to pop the head + // of the queue, override by returning mMainCtx. + next = mMainCtx; + + /*------------------------- logging stuff --------------------------*/ + // Unless this log tag is enabled, don't even bother posting. + LL_DEBUGS("LLCoros.scheduler"); + // This feature is inherently hard to verify. The logging in the + // lambda below seems useful, but also seems like a lot of overhead + // for a coroutine context switch. Try posting the logging lambda to a + // ThreadPool to offload that overhead. However, if this is still + // taking an unreasonable amount of context-switch time, this whole + // passage could be skipped. + + // Record this event for logging, but push it off to a thread pool to + // perform that work. Presumably std::weak_ptr::lock() is cheaper than + // WorkQueue::getInstance(). + LL::WorkQueue::ptr_t queue{ mQueue.lock() }; + // We probably started before the relevant WorkQueue was created. + if (! queue) + { + // Try again to locate the specified WorkQueue. + queue = LL::WorkQueue::getInstance(qname); + mQueue = queue; + } + // Both the lock() call and the getInstance() call might have failed. + if (queue) + { + // Bind values. Do NOT bind 'this' to avoid cross-thread access! + // It would be interesting to know from what queue position we + // unlinked the main fiber, out of how many in the ready list. + // Unfortunately round_robin::rqueue_ is private, not protected, + // so we have no access. + queue->post( + [switches=mSwitches, start=mStart, elapsed, now] + () + { + U32 runtime(U32(now) - U32(start)); + U32 minutes(runtime / 60u); + U32 seconds(runtime % 60u); + // use stringize to avoid lasting side effects to the + // logging ostream + LL_DEBUGS("LLCoros.scheduler") + << "At time " + << stringize(minutes, ":", std::setw(2), std::setfill('0'), seconds) + << " (" << switches << " switches), coroutines took " + << stringize(std::setprecision(4), elapsed) + << " sec, main coroutine jumped queue" + << LL_ENDL; + }); + } + LL_ENDL; + /*----------------------- end logging stuff ------------------------*/ + } + else + { + // Either the main fiber isn't yet ready, or it hasn't yet been + // mTimeslice seconds since the last time the main fiber ran. Business + // as usual. + next = super::pick_next(); + } + + // super::pick_next() could also have returned the main fiber, which is + // why this is a separate test instead of being folded into the override + // case above. + if (next && next->get_id() == mMainID) + { + // we're about to resume the main fiber: it's no longer "ready" + mMainCtx = nullptr; + // instead, it's "running" + mMainRunning = true; + } + return next; +} + +void scheduler::use() +{ + boost::fibers::use_scheduling_algorithm(); +} + +} // namespace llcoro diff --git a/indra/llcommon/coro_scheduler.h b/indra/llcommon/coro_scheduler.h new file mode 100644 index 0000000000..a7572ccf4d --- /dev/null +++ b/indra/llcommon/coro_scheduler.h @@ -0,0 +1,73 @@ +/** + * @file coro_scheduler.h + * @author Nat Goodspeed + * @date 2024-08-05 + * @brief Custom scheduler for viewer's Boost.Fibers (aka coroutines) + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_CORO_SCHEDULER_H) +#define LL_CORO_SCHEDULER_H + +#include "workqueue.h" +#include +#include + +/** + * llcoro::scheduler is specifically intended for the viewer's main thread. + * Its role is to ensure that the main coroutine, responsible for UI + * operations and coordinating everything else, doesn't get starved by + * secondary coroutines -- however many of those there might be. + * + * The simple boost::fibers::algo::round_robin scheduler could result in + * arbitrary time lag between resumptions of the main coroutine. Of course + * every well-behaved viewer coroutine must be coded to yield before too much + * real time has elapsed, but sheer volume of secondary coroutines could still + * consume unreasonable real time before cycling back to the main coroutine. + */ + +namespace llcoro +{ + +class scheduler: public boost::fibers::algo::round_robin +{ + using super = boost::fibers::algo::round_robin; +public: + // If the main fiber is ready, and it's been at least this long since the + // main fiber last ran, jump the main fiber to the head of the queue. + static const F32 DEFAULT_TIMESLICE; + + scheduler(); + void awakened( boost::fibers::context*) noexcept override; + boost::fibers::context* pick_next() noexcept override; + + static void use(); + +private: + // This is the fiber::id of the main fiber. We use this to discover + // whether the fiber passed to awakened() is in fact the main fiber. + boost::fibers::fiber::id mMainID; + // This context* is nullptr until awakened() notices that the main fiber + // has become ready, at which point it contains the main fiber's context*. + boost::fibers::context* mMainCtx{}; + // Set when pick_next() returns the main fiber. + bool mMainRunning{ false }; + // If it's been at least this long since the last time the main fiber got + // control, jump it to the head of the queue. + F32 mTimeslice{ DEFAULT_TIMESLICE }; + // Timestamp as of the last time we suspended the main fiber. + F32 mMainLast{ 0 }; + // Timestamp of start time + F32 mStart{ 0 }; + // count context switches + U64 mSwitches{ 0 }; + // WorkQueue for deferred logging + LL::WorkQueue::weak_t mQueue; +}; + +} // namespace llcoro + +#endif /* ! defined(LL_CORO_SCHEDULER_H) */ diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 3cf0def66e..c7fb734440 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -48,6 +48,7 @@ #include "llaudioengine_openal.h" #endif +#include "coro_scheduler.h" #include "llavatarnamecache.h" #include "llexperiencecache.h" #include "lllandmark.h" @@ -400,10 +401,12 @@ bool idle_startup() static bool first_call = true; if (first_call) { + first_call = false; // Other phases get handled when startup state changes, // need to capture the initial state as well. LLStartUp::getPhases().startPhase(LLStartUp::getStartupStateString()); - first_call = false; + // Use our custom scheduler for coroutine scheduling. + llcoro::scheduler::use(); } gViewerWindow->showCursor(); -- cgit v1.2.3 From 5fde031b876e3b8ce794286e5796ffb8f0614cd3 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 7 Aug 2024 10:39:30 -0400 Subject: Rename 'UI' 'getParents' op to 'getTopMenus', add UI.lua function. Also update the 'UI' help text to reflect its more general nature. Mention 0-relative rank in the xxToolbarBtn operation help text. --- indra/newview/lluilistener.cpp | 23 ++++++++++++----------- indra/newview/lluilistener.h | 2 +- indra/newview/scripts/lua/require/UI.lua | 4 ++++ 3 files changed, 17 insertions(+), 12 deletions(-) (limited to 'indra') diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp index 3e67531388..0280836075 100644 --- a/indra/newview/lluilistener.cpp +++ b/indra/newview/lluilistener.cpp @@ -48,8 +48,7 @@ extern LLMenuBarGL* gMenuBarView; LLUIListener::LLUIListener(): LLEventAPI("UI", - "LLUICtrl::CommitCallbackRegistry listener.\n" - "Capable of invoking any function (with parameter) you can specify in XUI.") + "Operations to manipulate the viewer's user interface.") { add("call", "Invoke the operation named by [\"function\"], passing [\"parameter\"],\n" @@ -70,9 +69,9 @@ LLUIListener::LLUIListener(): &LLUIListener::getValue, llsd::map("path", LLSD(), "reply", LLSD())); - add("getParents", + add("getTopMenus", "List names of Top menus suitable for passing as \"parent_menu\"", - &LLUIListener::getParents, + &LLUIListener::getTopMenus, llsd::map("reply", LLSD::String())); LLSD required_args = llsd::map("name", LLSD(), "label", LLSD(), "reply", LLSD()); @@ -116,13 +115,15 @@ LLUIListener::LLUIListener(): add("addToolbarBtn", "Add [\"btn_name\"] toolbar button to the [\"toolbar\"]:\n" "\"left\", \"right\", \"bottom\" (default is \"bottom\")\n" - "Position of the command in the original list can be specified as [\"rank\"]", + "Position of the command in the original list can be specified as [\"rank\"],\n" + "where 0 means the first item", &LLUIListener::addToolbarBtn, llsd::map("btn_name", LLSD(), "reply", LLSD())); add("removeToolbarBtn", "Remove [\"btn_name\"] toolbar button off the toolbar,\n" "return [\"rank\"] (old position) of the command in the original list,\n" + "rank 0 is the first position,\n" "rank -1 means that [\"btn_name\"] was not found", &LLUIListener::removeToolbarBtn, llsd::map("btn_name", LLSD(), "reply", LLSD())); @@ -141,7 +142,7 @@ typedef LLUICtrl::CommitCallbackInfo cb_info; void LLUIListener::call(const LLSD& event) { Response response(LLSD(), event); - LLUICtrl::CommitCallbackInfo *info = LLUICtrl::CommitCallbackRegistry::getValue(event["function"]); + cb_info *info = LLUICtrl::CommitCallbackRegistry::getValue(event["function"]); if (!info || !info->callback_func) { return response.error(stringize("Function ", std::quoted(event["function"].asString()), " was not found")); @@ -201,13 +202,13 @@ void LLUIListener::callables(const LLSD& event) const LLSD entry{ llsd::map("name", it->first) }; switch (it->second.handle_untrusted) { - case LLUICtrl::CommitCallbackInfo::UNTRUSTED_ALLOW: + case cb_info::UNTRUSTED_ALLOW: entry["access"] = "allow"; break; - case LLUICtrl::CommitCallbackInfo::UNTRUSTED_BLOCK: + case cb_info::UNTRUSTED_BLOCK: entry["access"] = "block"; break; - case LLUICtrl::CommitCallbackInfo::UNTRUSTED_THROTTLE: + case cb_info::UNTRUSTED_THROTTLE: entry["access"] = "throttle"; break; } @@ -235,10 +236,10 @@ void LLUIListener::getValue(const LLSD& event) const } } -void LLUIListener::getParents(const LLSD& event) const +void LLUIListener::getTopMenus(const LLSD& event) const { Response response(LLSD(), event); - response["parents"] = llsd::toArray( + response["menus"] = llsd::toArray( *gMenuBarView->getChildList(), [](auto childp) {return childp->getName(); }); } diff --git a/indra/newview/lluilistener.h b/indra/newview/lluilistener.h index 98e4754306..c839c03a57 100644 --- a/indra/newview/lluilistener.h +++ b/indra/newview/lluilistener.h @@ -43,7 +43,7 @@ private: void call(const LLSD& event); void callables(const LLSD& event) const; void getValue(const LLSD& event) const; - void getParents(const LLSD& event) const; + void getTopMenus(const LLSD& event) const; void addMenu(const LLSD&event) const; void addMenuBranch(const LLSD&event) const; diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua index 9bc9a3685d..464e6547ea 100644 --- a/indra/newview/scripts/lua/require/UI.lua +++ b/indra/newview/scripts/lua/require/UI.lua @@ -144,6 +144,10 @@ end -- Top menu -- *************************************************************************** +function UI.getTopMenus() + return leap.request('UI', {op='getTopMenus'}).menus +end + function UI.addMenu(...) local args = mapargs('name,label', ...) args.op = 'addMenu' -- cgit v1.2.3 From 2df6ee631507bd3c9bff783d57b96d4546405a8a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 7 Aug 2024 12:51:47 -0400 Subject: Fix omission in login.savedLogins(). Also add Region.lua. --- indra/newview/scripts/lua/require/Region.lua | 17 +++++++++++++++++ indra/newview/scripts/lua/require/login.lua | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 indra/newview/scripts/lua/require/Region.lua (limited to 'indra') diff --git a/indra/newview/scripts/lua/require/Region.lua b/indra/newview/scripts/lua/require/Region.lua new file mode 100644 index 0000000000..e4eefece33 --- /dev/null +++ b/indra/newview/scripts/lua/require/Region.lua @@ -0,0 +1,17 @@ +LLFloaterAbout = require 'LLFloaterAbout' + +local Region = {} + +function Region.getInfo() + info = LLFloaterAbout.getInfo() + return { + HOSTNAME=info.HOSTNAME, + POSITION=info.POSITION, + POSITION_LOCAL=info.POSITION_LOCAL, + REGION=info.REGION, + SERVER_VERSION=info.SERVER_VERSION, + SLURL=info.SLURL, + } +end + +return Region diff --git a/indra/newview/scripts/lua/require/login.lua b/indra/newview/scripts/lua/require/login.lua index 50fc9e3793..919434f3a5 100644 --- a/indra/newview/scripts/lua/require/login.lua +++ b/indra/newview/scripts/lua/require/login.lua @@ -27,7 +27,7 @@ end function login.savedLogins(grid) ensure_login_state('savedLogins') - return leap.request('LLPanelLogin', {op='savedLogins'})['logins'] + return leap.request('LLPanelLogin', {op='savedLogins', grid=grid})['logins'] end return login -- cgit v1.2.3 From 08def53d8bf07beaa56db80e95aa76f3682c557c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 7 Aug 2024 16:01:45 -0400 Subject: Move llcoro::scheduler::use() call from llstartup to llappviewer. Thanks, Maxim. --- indra/newview/llappviewer.cpp | 3 ++- indra/newview/llstartup.cpp | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'indra') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index ff29f64aeb..3d53200eda 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -765,7 +765,8 @@ bool LLAppViewer::init() //set the max heap size. initMaxHeapSize() ; LLCoros::instance().setStackSize(gSavedSettings.getS32("CoroutineStackSize")); - + // Use our custom scheduler for coroutine scheduling. + llcoro::scheduler::use(); // Although initLoggingAndGetLastDuration() is the right place to mess with // setFatalFunction(), we can't query gSavedSettings until after diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index c7fb734440..82eb59c002 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -405,8 +405,6 @@ bool idle_startup() // Other phases get handled when startup state changes, // need to capture the initial state as well. LLStartUp::getPhases().startPhase(LLStartUp::getStartupStateString()); - // Use our custom scheduler for coroutine scheduling. - llcoro::scheduler::use(); } gViewerWindow->showCursor(); -- cgit v1.2.3 From 3102dc0db472acac7c4007e7423e405a889d665a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 7 Aug 2024 16:12:23 -0400 Subject: Allow smaller minimum timer intervals. Add test_flycam.lua to exercise the smaller intervals. --- indra/llcommon/llcallbacklist.cpp | 2 +- indra/newview/scripts/lua/test_flycam.lua | 38 +++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 indra/newview/scripts/lua/test_flycam.lua (limited to 'indra') diff --git a/indra/llcommon/llcallbacklist.cpp b/indra/llcommon/llcallbacklist.cpp index 555c793333..647b268b8b 100644 --- a/indra/llcommon/llcallbacklist.cpp +++ b/indra/llcommon/llcallbacklist.cpp @@ -391,7 +391,7 @@ public: TimersListener(const LazyEventAPIParams& params): LLEventAPI(params) {} // Forbid a script from requesting callbacks too quickly. - static constexpr LLSD::Real MINTIMER{ 1.0 }; + static constexpr LLSD::Real MINTIMER{ 0.010 }; void scheduleAfter(const LLSD& params); void scheduleEvery(const LLSD& params); diff --git a/indra/newview/scripts/lua/test_flycam.lua b/indra/newview/scripts/lua/test_flycam.lua new file mode 100644 index 0000000000..4fb5608b33 --- /dev/null +++ b/indra/newview/scripts/lua/test_flycam.lua @@ -0,0 +1,38 @@ +-- Make camera fly around the subject avatar for a few seconds. + +local LLAgent = require 'LLAgent' +local startup = require 'startup' +local timers = require 'timers' + +local initial = LLAgent.getRegionPosition() +local height = 2.0 -- meters +local radius = 4.0 -- meters +local speed = 1.0 -- meters/second along circle +local start = os.clock() +local stop = os.clock() + 30 -- seconds + +local function cameraPos(t) + local radians = speed * t + return { + initial[1] + radius * math.cos(radians), + initial[2] + radius * math.sin(radians), + initial[3] + height + } +end + +local function moveCamera() + if os.clock() < stop then + -- usual case + LLAgent.setCamera{ camera_pos=cameraPos(os.clock() - start), camera_locked=true } + return nil + else + -- last time + LLAgent.removeCamParams() + LLAgent.setFollowCamActive(false) + return true + end +end + +startup.wait('STATE_STARTED') +-- call moveCamera() repeatedly until it returns true +local timer = timers.Timer(0.1, moveCamera, true) -- cgit v1.2.3 From cdc4f5dbd69526906724dc298594906826f6b8b0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 7 Aug 2024 16:34:14 -0400 Subject: Move #include "coro_scheduler.h" from llstartup to llappviewer. --- indra/newview/llappviewer.cpp | 1 + indra/newview/llstartup.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 3d53200eda..c259275d8f 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -29,6 +29,7 @@ #include "llappviewer.h" // Viewer includes +#include "coro_scheduler.h" #include "llversioninfo.h" #include "llfeaturemanager.h" #include "lluictrlfactory.h" diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 82eb59c002..d49e0d9ba2 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -48,7 +48,6 @@ #include "llaudioengine_openal.h" #endif -#include "coro_scheduler.h" #include "llavatarnamecache.h" #include "llexperiencecache.h" #include "lllandmark.h" -- cgit v1.2.3 From b4fe47a5c0abac02d161640e04a9a78afb1c5987 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 8 Aug 2024 09:07:56 -0400 Subject: Ensure that the flycam stays near moving avatar. --- indra/newview/scripts/lua/test_flycam.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/test_flycam.lua b/indra/newview/scripts/lua/test_flycam.lua index 4fb5608b33..05c3c37b93 100644 --- a/indra/newview/scripts/lua/test_flycam.lua +++ b/indra/newview/scripts/lua/test_flycam.lua @@ -4,7 +4,6 @@ local LLAgent = require 'LLAgent' local startup = require 'startup' local timers = require 'timers' -local initial = LLAgent.getRegionPosition() local height = 2.0 -- meters local radius = 4.0 -- meters local speed = 1.0 -- meters/second along circle @@ -12,11 +11,12 @@ local start = os.clock() local stop = os.clock() + 30 -- seconds local function cameraPos(t) + local agent = LLAgent.getRegionPosition() local radians = speed * t return { - initial[1] + radius * math.cos(radians), - initial[2] + radius * math.sin(radians), - initial[3] + height + agent[1] + radius * math.cos(radians), + agent[2] + radius * math.sin(radians), + agent[3] + height } end -- cgit v1.2.3 From 087cbe553e5bac6fe702200c33acc42baf4eef4f Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 9 Aug 2024 15:00:04 +0300 Subject: Lua api for sending group messages --- indra/newview/groupchatlistener.cpp | 100 +++++++++++++++++++-------- indra/newview/groupchatlistener.h | 21 ++++-- indra/newview/llgroupactions.cpp | 8 ++- indra/newview/lltoastimpanel.cpp | 4 +- indra/newview/scripts/lua/require/LLChat.lua | 25 +++++++ 5 files changed, 119 insertions(+), 39 deletions(-) (limited to 'indra') diff --git a/indra/newview/groupchatlistener.cpp b/indra/newview/groupchatlistener.cpp index 43507f13e9..ab367d9fa1 100644 --- a/indra/newview/groupchatlistener.cpp +++ b/indra/newview/groupchatlistener.cpp @@ -2,11 +2,11 @@ * @file groupchatlistener.cpp * @author Nat Goodspeed * @date 2011-04-11 - * @brief Implementation for groupchatlistener. + * @brief Implementation for LLGroupChatListener. * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. + * Copyright (C) 2024, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -34,43 +34,85 @@ // std headers // external library headers // other Linden headers +#include "llchat.h" #include "llgroupactions.h" #include "llimview.h" +static const F32 GROUP_CHAT_THROTTLE_PERIOD = 1.f; -namespace { - void startIm_wrapper(LLSD const & event) +LLGroupChatListener::LLGroupChatListener(): + LLEventAPI("GroupChat", + "API to enter, leave, send and intercept group chat messages"), + mLastThrottleTime(0) +{ + add("startGroupChat", + "Enter a group chat in group with UUID [\"group_id\"]\n" + "Assumes the logged-in agent is already a member of this group.", + &LLGroupChatListener::startGroupChat, + llsd::map("group_id", LLSD())); + add("leaveGroupChat", + "Leave a group chat in group with UUID [\"group_id\"]\n" + "Assumes a prior successful startIM request.", + &LLGroupChatListener::leaveGroupChat, + llsd::map("group_id", LLSD())); + add("sendGroupIM", + "send a groupchat IM", + &LLGroupChatListener::sendGroupIM, + llsd::map("message", LLSD(), "group_id", LLSD())); +} + +bool is_in_group(LLEventAPI::Response &response, const LLSD &data) +{ + if (!LLGroupActions::isInGroup(data["group_id"])) { - LLUUID session_id = LLGroupActions::startIM(event["id"].asUUID()); - sendReply(LLSDMap("session_id", LLSD(session_id)), event); + response.error(stringize("You are not the member of the group:", std::quoted(data["group_id"].asString()))); + return false; } + return true; +} - void send_message_wrapper(const std::string& text, const LLUUID& session_id, const LLUUID& group_id) +void LLGroupChatListener::startGroupChat(LLSD const &data) +{ + Response response(LLSD(), data); + if (!is_in_group(response, data)) { - LLIMModel::sendMessage(text, session_id, group_id, IM_SESSION_GROUP_START); + return; + } + if (LLGroupActions::startIM(data["group_id"]).isNull()) + { + return response.error(stringize("Failed to start group chat session ", std::quoted(data["group_id"].asString()))); } } +void LLGroupChatListener::leaveGroupChat(LLSD const &data) +{ + Response response(LLSD(), data); + if (is_in_group(response, data)) + { + LLGroupActions::endIM(data["group_id"].asUUID()); + } +} -GroupChatListener::GroupChatListener(): - LLEventAPI("GroupChat", - "API to enter, leave, send and intercept group chat messages") +void LLGroupChatListener::sendGroupIM(LLSD const &data) { - add("startIM", - "Enter a group chat in group with UUID [\"id\"]\n" - "Assumes the logged-in agent is already a member of this group.", - &startIm_wrapper); - add("endIM", - "Leave a group chat in group with UUID [\"id\"]\n" - "Assumes a prior successful startIM request.", - &LLGroupActions::endIM, - llsd::array("id")); - add("sendIM", - "send a groupchat IM", - &send_message_wrapper, - llsd::array("text", "session_id", "group_id")); + Response response(LLSD(), data); + if (!is_in_group(response, data)) + { + return; + } + + F64 cur_time = LLTimer::getElapsedSeconds(); + + if (cur_time < mLastThrottleTime + GROUP_CHAT_THROTTLE_PERIOD) + { + LL_DEBUGS("LLGroupChatListener") << "'sendGroupIM' was throttled" << LL_ENDL; + return; + } + mLastThrottleTime = cur_time; + + LLUUID group_id(data["group_id"]); + LLIMModel::sendMessage(LUA_PREFIX + data["message"].asString(), + gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id), + group_id, + IM_SESSION_GROUP_START); } -/* - static void sendMessage(const std::string& utf8_text, const LLUUID& im_session_id, - const LLUUID& other_participant_id, EInstantMessage dialog); -*/ diff --git a/indra/newview/groupchatlistener.h b/indra/newview/groupchatlistener.h index 3819ac59b7..35afc5766c 100644 --- a/indra/newview/groupchatlistener.h +++ b/indra/newview/groupchatlistener.h @@ -4,9 +4,9 @@ * @date 2011-04-11 * @brief * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. + * Copyright (C) 2024, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -26,15 +26,22 @@ * $/LicenseInfo$ */ -#if ! defined(LL_GROUPCHATLISTENER_H) -#define LL_GROUPCHATLISTENER_H +#if ! defined(LL_LLGROUPCHATLISTENER_H) +#define LL_LLGROUPCHATLISTENER_H #include "lleventapi.h" -class GroupChatListener: public LLEventAPI +class LLGroupChatListener: public LLEventAPI { public: - GroupChatListener(); + LLGroupChatListener(); + +private: + void startGroupChat(LLSD const &data); + void leaveGroupChat(LLSD const &data); + void sendGroupIM(LLSD const &data); + + F64 mLastThrottleTime {0.0}; }; -#endif /* ! defined(LL_GROUPCHATLISTENER_H) */ +#endif /* ! defined(LL_LLGROUPCHATLISTENER_H) */ diff --git a/indra/newview/llgroupactions.cpp b/indra/newview/llgroupactions.cpp index 24ae90e3ae..e901631d94 100644 --- a/indra/newview/llgroupactions.cpp +++ b/indra/newview/llgroupactions.cpp @@ -37,6 +37,7 @@ #include "llfloatersidepanelcontainer.h" #include "llgroupmgr.h" #include "llfloaterimcontainer.h" +#include "llfloaterimsession.h" #include "llimview.h" // for gIMMgr #include "llnotificationsutil.h" #include "llstartup.h" @@ -46,7 +47,7 @@ // // Globals // -static GroupChatListener sGroupChatListener; +static LLGroupChatListener sGroupChatListener; class LLGroupHandler : public LLCommandHandler { @@ -519,7 +520,10 @@ void LLGroupActions::endIM(const LLUUID& group_id) LLUUID session_id = gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id); if (session_id != LLUUID::null) { - gIMMgr->leaveSession(session_id); + if (LLFloaterIMSession *conversationFloater = LLFloaterIMSession::findInstance(session_id)) + { + LLFloater::onClickClose(conversationFloater); + } } } diff --git a/indra/newview/lltoastimpanel.cpp b/indra/newview/lltoastimpanel.cpp index f7e2d49e13..7fadd8773b 100644 --- a/indra/newview/lltoastimpanel.cpp +++ b/indra/newview/lltoastimpanel.cpp @@ -87,7 +87,9 @@ LLToastIMPanel::LLToastIMPanel(LLToastIMPanel::Params &p) : LLToastPanel(p.notif { LLAvatarName avatar_name; LLAvatarNameCache::get(p.avatar_id, &avatar_name); - p.message = "[From " + avatar_name.getDisplayName() + "]\n" + p.message; + auto [message, is_lua] = LLStringUtil::withoutPrefix(p.message, LUA_PREFIX); + std::string prefix = is_lua ? "LUA - " : ""; + p.message = "[From " + prefix + avatar_name.getDisplayName() + "]\n" + message; } style_params.font.style = "NORMAL"; mMessage->setText(p.message, style_params); diff --git a/indra/newview/scripts/lua/require/LLChat.lua b/indra/newview/scripts/lua/require/LLChat.lua index 78dca765e8..ea6329d574 100644 --- a/indra/newview/scripts/lua/require/LLChat.lua +++ b/indra/newview/scripts/lua/require/LLChat.lua @@ -2,6 +2,10 @@ local leap = require 'leap' local LLChat = {} +-- *************************************************************************** +-- Nearby chat +-- *************************************************************************** + function LLChat.sendNearby(msg) leap.send('LLChatBar', {op='sendChat', message=msg}) end @@ -14,4 +18,25 @@ function LLChat.sendShout(msg) leap.send('LLChatBar', {op='sendChat', type='shout', message=msg}) end +-- 0 is public nearby channel, other channels are used to communicate with LSL scripts +function LLChat.sendChannel(msg, channel) + leap.send('LLChatBar', {op='sendChat', channel=channel, message=msg}) +end + +-- *************************************************************************** +-- Group chat +-- *************************************************************************** + +function LLChat.startGroupChat(group_id) + return leap.request('GroupChat', {op='startGroupChat', group_id=group_id}) +end + +function LLChat.leaveGroupChat(group_id) + leap.send('GroupChat', {op='leaveGroupChat', group_id=group_id}) +end + +function LLChat.sendGroupIM(msg, group_id) + leap.send('GroupChat', {op='sendGroupIM', message=msg, group_id=group_id}) +end + return LLChat -- cgit v1.2.3 From 926a32aa0a9518fe7f19d0c63b30b12a66469f2d Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 9 Aug 2024 18:24:25 +0300 Subject: add demo script for sending group chat messages --- indra/newview/groupchatlistener.cpp | 4 ++-- indra/newview/scripts/lua/require/LLAgent.lua | 11 +++++++++++ indra/newview/scripts/lua/test_group_chat.lua | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 indra/newview/scripts/lua/test_group_chat.lua (limited to 'indra') diff --git a/indra/newview/groupchatlistener.cpp b/indra/newview/groupchatlistener.cpp index ab367d9fa1..09951ba1cc 100644 --- a/indra/newview/groupchatlistener.cpp +++ b/indra/newview/groupchatlistener.cpp @@ -56,7 +56,7 @@ LLGroupChatListener::LLGroupChatListener(): &LLGroupChatListener::leaveGroupChat, llsd::map("group_id", LLSD())); add("sendGroupIM", - "send a groupchat IM", + "send a [\"message\"] to group with UUID [\"group_id\"]", &LLGroupChatListener::sendGroupIM, llsd::map("message", LLSD(), "group_id", LLSD())); } @@ -114,5 +114,5 @@ void LLGroupChatListener::sendGroupIM(LLSD const &data) LLIMModel::sendMessage(LUA_PREFIX + data["message"].asString(), gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id), group_id, - IM_SESSION_GROUP_START); + IM_SESSION_SEND); } diff --git a/indra/newview/scripts/lua/require/LLAgent.lua b/indra/newview/scripts/lua/require/LLAgent.lua index bc9a6b23a0..5ee092f2f6 100644 --- a/indra/newview/scripts/lua/require/LLAgent.lua +++ b/indra/newview/scripts/lua/require/LLAgent.lua @@ -11,6 +11,17 @@ function LLAgent.getGlobalPosition() return leap.request('LLAgent', {op = 'getPosition'}).global end +-- Return array information about the agent's groups +-- id: group id\n" +-- name: group name\n" +-- insignia: group insignia texture id +-- notices: bool indicating if this user accepts notices from this group +-- display: bool indicating if this group is listed in the user's profile +-- contrib: user's land contribution to this group +function LLAgent.getGroups() + return leap.request('LLAgent', {op = 'getGroups'}).groups +end + -- Use LL.leaphelp('LLAgent') and see 'setCameraParams' to get more info about params -- -- TYPE -- DEFAULT -- RANGE -- LLAgent.setCamera{ [, camera_pos] -- vector3 diff --git a/indra/newview/scripts/lua/test_group_chat.lua b/indra/newview/scripts/lua/test_group_chat.lua new file mode 100644 index 0000000000..6299ba535f --- /dev/null +++ b/indra/newview/scripts/lua/test_group_chat.lua @@ -0,0 +1,16 @@ +LLChat = require 'LLChat' +inspect = require 'inspect' +LLAgent = require 'LLAgent' +popup = require 'popup' + +local OK = 'OK_okcancelbuttons' +local GROUPS = LLAgent.getGroups() + +-- Choose one of the groups randomly and send group message +math.randomseed(os.time()) +group_info = GROUPS[math.random(#GROUPS)] +LLChat.startGroupChat(group_info.id) +response = popup:alertYesCancel('Started group chat with ' .. group_info.name .. ' group. Send greetings?') +if next(response) == OK then + LLChat.sendGroupIM('Greetings', group_info.id) +end -- cgit v1.2.3 From a596c1ad2864d639bbab13e7f13fd9f029cf5d93 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 12 Aug 2024 15:58:03 -0400 Subject: Add Throttle and LogThrottle classes to manage throttled APIs. --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/throttle.cpp | 34 +++++++++ indra/llcommon/throttle.h | 137 ++++++++++++++++++++++++++++++++++++ indra/newview/groupchatlistener.cpp | 19 +++-- indra/newview/groupchatlistener.h | 4 +- 5 files changed, 184 insertions(+), 12 deletions(-) create mode 100644 indra/llcommon/throttle.cpp create mode 100644 indra/llcommon/throttle.h (limited to 'indra') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 20670d7ebe..4c4a676531 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -112,6 +112,7 @@ set(llcommon_SOURCE_FILES lua_function.cpp lualistener.cpp threadpool.cpp + throttle.cpp u64.cpp workqueue.cpp StackWalker.cpp @@ -263,6 +264,7 @@ set(llcommon_HEADER_FILES threadpool.h threadpool_fwd.h threadsafeschedule.h + throttle.h timer.h tuple.h u64.h diff --git a/indra/llcommon/throttle.cpp b/indra/llcommon/throttle.cpp new file mode 100644 index 0000000000..d5f7d5dcab --- /dev/null +++ b/indra/llcommon/throttle.cpp @@ -0,0 +1,34 @@ +/** + * @file throttle.cpp + * @author Nat Goodspeed + * @date 2024-08-12 + * @brief Implementation for ThrottleBase. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "throttle.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "lltimer.h" + +bool ThrottleBase::too_fast() +{ + F64 now = LLTimer::getElapsedSeconds(); + if (now < mNext) + { + return true; + } + else + { + mNext = now + mInterval; + return false; + } +} diff --git a/indra/llcommon/throttle.h b/indra/llcommon/throttle.h new file mode 100644 index 0000000000..abf595e3c4 --- /dev/null +++ b/indra/llcommon/throttle.h @@ -0,0 +1,137 @@ +/** + * @file throttle.h + * @author Nat Goodspeed + * @date 2024-08-12 + * @brief Throttle class + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_THROTTLE_H) +#define LL_THROTTLE_H + +#include "apply.h" // LL::bind_front() +#include "llerror.h" +#include +#include // std::quoted() + +class ThrottleBase +{ +public: + ThrottleBase(F64 interval): + mInterval(interval) + {} + +protected: + bool too_fast(); // not const: we update mNext + F64 mInterval, mNext{ 0. }; +}; + +/** + * An instance of Throttle mediates calls to some other specified function, + * ensuring that it's called no more often than the specified time interval. + * Throttle is an abstract base class that delegates the behavior when the + * specified interval is exceeded. + */ +template +class Throttle: public ThrottleBase +{ +public: + Throttle(const std::string& desc, + const std::function& func, + F64 interval): + ThrottleBase(interval), + mDesc(desc), + mFunc(func) + {} + // Constructing Throttle with a member function pointer but without an + // instance pointer requires you to pass the instance pointer/reference as + // the first argument to operator()(). + template + Throttle(const std::string& desc, R C::* method, F64 interval): + Throttle(desc, std::mem_fn(method), interval) + {} + template + Throttle(const std::string& desc, R C::* method, C* instance, F64 interval): + Throttle(desc, LL::bind_front(method, instance), interval) + {} + template + Throttle(const std::string& desc, R C::* method, const C* instance, F64 interval): + Throttle(desc, LL::bind_front(method, instance), interval) + {} + + template + auto operator()(ARGS... args) + { + if (too_fast()) + { + suppress(); + using rtype = decltype(mFunc(std::forward(args)...)); + if constexpr (! std::is_same_v) + { + return rtype{}; + } + } + else + { + return mFunc(std::forward(args)...); + } + } + +protected: + // override with desired behavior when calls come too often + virtual void suppress() = 0; + const std::string mDesc; + +private: + std::function mFunc; +}; + +/** + * An instance of LogThrottle mediates calls to some other specified function, + * ensuring that it's called no more often than the specified time interval. + * When that interval is exceeded, it logs a message at the specified log + * level. It uses LL_MUMBLES_ONCE() logic to prevent spamming, since a too- + * frequent call may well be spammy. + */ +template +class LogThrottle: public Throttle +{ + using super = Throttle; +public: + LogThrottle(const std::string& desc, + const std::function& func, + F64 interval): + super(desc, func, interval) + {} + template + LogThrottle(const std::string& desc, R C::* method, F64 interval): + super(desc, method, interval) + {} + template + LogThrottle(const std::string& desc, R C::* method, C* instance, F64 interval): + super(desc, method, instance, interval) + {} + template + LogThrottle(const std::string& desc, R C::* method, const C* instance, F64 interval): + super(desc, method, instance, interval) + {} + +private: + void suppress() override + { + // Using lllog(), the macro underlying LL_WARNS() et al., allows + // specifying compile-time LOGLEVEL. It does NOT support a variable + // LOGLEVEL, which is why LOGLEVEL is a non-type template parameter. + // See llvlog() for variable support, which is a bit more expensive. + // true = only print the log message once + lllog(LOGLEVEL, true, "LogThrottle") << std::quoted(super::mDesc) + << " called more than once per " + << super::mInterval + << LL_ENDL; + } +}; + +#endif /* ! defined(LL_THROTTLE_H) */ diff --git a/indra/newview/groupchatlistener.cpp b/indra/newview/groupchatlistener.cpp index 09951ba1cc..298f41ff8c 100644 --- a/indra/newview/groupchatlistener.cpp +++ b/indra/newview/groupchatlistener.cpp @@ -43,7 +43,8 @@ static const F32 GROUP_CHAT_THROTTLE_PERIOD = 1.f; LLGroupChatListener::LLGroupChatListener(): LLEventAPI("GroupChat", "API to enter, leave, send and intercept group chat messages"), - mLastThrottleTime(0) + mIMThrottle("sendGroupIM", &LLGroupChatListener::sendGroupIM_, this, + GROUP_CHAT_THROTTLE_PERIOD) { add("startGroupChat", "Enter a group chat in group with UUID [\"group_id\"]\n" @@ -101,18 +102,14 @@ void LLGroupChatListener::sendGroupIM(LLSD const &data) return; } - F64 cur_time = LLTimer::getElapsedSeconds(); - - if (cur_time < mLastThrottleTime + GROUP_CHAT_THROTTLE_PERIOD) - { - LL_DEBUGS("LLGroupChatListener") << "'sendGroupIM' was throttled" << LL_ENDL; - return; - } - mLastThrottleTime = cur_time; + mIMThrottle(data["group_id"], data["message"]); +} - LLUUID group_id(data["group_id"]); - LLIMModel::sendMessage(LUA_PREFIX + data["message"].asString(), +void LLGroupChatListener::sendGroupIM_(const LLUUID& group_id, const std::string& message) +{ + LLIMModel::sendMessage(LUA_PREFIX + message, gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id), group_id, IM_SESSION_SEND); } + diff --git a/indra/newview/groupchatlistener.h b/indra/newview/groupchatlistener.h index 35afc5766c..a75fecb254 100644 --- a/indra/newview/groupchatlistener.h +++ b/indra/newview/groupchatlistener.h @@ -30,6 +30,7 @@ #define LL_LLGROUPCHATLISTENER_H #include "lleventapi.h" +#include "throttle.h" class LLGroupChatListener: public LLEventAPI { @@ -40,8 +41,9 @@ private: void startGroupChat(LLSD const &data); void leaveGroupChat(LLSD const &data); void sendGroupIM(LLSD const &data); + void sendGroupIM_(const LLUUID& group_id, const std::string& message); - F64 mLastThrottleTime {0.0}; + LogThrottle mIMThrottle; }; #endif /* ! defined(LL_LLGROUPCHATLISTENER_H) */ -- cgit v1.2.3 From 153b0573cd91361a0f674c0ce1b9d7cb47e2bbc0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 12 Aug 2024 16:28:44 -0400 Subject: Add virtual destructor to Throttle class. --- indra/llcommon/throttle.h | 1 + 1 file changed, 1 insertion(+) (limited to 'indra') diff --git a/indra/llcommon/throttle.h b/indra/llcommon/throttle.h index abf595e3c4..481d1d8f72 100644 --- a/indra/llcommon/throttle.h +++ b/indra/llcommon/throttle.h @@ -61,6 +61,7 @@ public: Throttle(const std::string& desc, R C::* method, const C* instance, F64 interval): Throttle(desc, LL::bind_front(method, instance), interval) {} + virtual ~Throttle() {} template auto operator()(ARGS... args) -- cgit v1.2.3 From 9f2e322c7eea6830d372943d74f986d299cd314a Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Tue, 13 Aug 2024 14:50:04 +0300 Subject: clean up and add comment --- indra/newview/groupchatlistener.cpp | 2 +- indra/newview/lltoastimpanel.cpp | 1 + indra/newview/scripts/lua/require/LLChat.lua | 10 +++------- indra/newview/scripts/lua/test_group_chat.lua | 1 - 4 files changed, 5 insertions(+), 9 deletions(-) (limited to 'indra') diff --git a/indra/newview/groupchatlistener.cpp b/indra/newview/groupchatlistener.cpp index 298f41ff8c..e48cbe824a 100644 --- a/indra/newview/groupchatlistener.cpp +++ b/indra/newview/groupchatlistener.cpp @@ -38,7 +38,7 @@ #include "llgroupactions.h" #include "llimview.h" -static const F32 GROUP_CHAT_THROTTLE_PERIOD = 1.f; +static const F64 GROUP_CHAT_THROTTLE_PERIOD = 1.f; LLGroupChatListener::LLGroupChatListener(): LLEventAPI("GroupChat", diff --git a/indra/newview/lltoastimpanel.cpp b/indra/newview/lltoastimpanel.cpp index 7fadd8773b..b7353d6960 100644 --- a/indra/newview/lltoastimpanel.cpp +++ b/indra/newview/lltoastimpanel.cpp @@ -87,6 +87,7 @@ LLToastIMPanel::LLToastIMPanel(LLToastIMPanel::Params &p) : LLToastPanel(p.notif { LLAvatarName avatar_name; LLAvatarNameCache::get(p.avatar_id, &avatar_name); + // move Lua prefix from the message field to the [From] field auto [message, is_lua] = LLStringUtil::withoutPrefix(p.message, LUA_PREFIX); std::string prefix = is_lua ? "LUA - " : ""; p.message = "[From " + prefix + avatar_name.getDisplayName() + "]\n" + message; diff --git a/indra/newview/scripts/lua/require/LLChat.lua b/indra/newview/scripts/lua/require/LLChat.lua index ea6329d574..bc0fc86d22 100644 --- a/indra/newview/scripts/lua/require/LLChat.lua +++ b/indra/newview/scripts/lua/require/LLChat.lua @@ -6,8 +6,9 @@ local LLChat = {} -- Nearby chat -- *************************************************************************** -function LLChat.sendNearby(msg) - leap.send('LLChatBar', {op='sendChat', message=msg}) +-- 0 is public nearby channel, other channels are used to communicate with LSL scripts +function LLChat.sendNearby(msg, channel) + leap.send('LLChatBar', {op='sendChat', message=msg, channel=channel}) end function LLChat.sendWhisper(msg) @@ -18,11 +19,6 @@ function LLChat.sendShout(msg) leap.send('LLChatBar', {op='sendChat', type='shout', message=msg}) end --- 0 is public nearby channel, other channels are used to communicate with LSL scripts -function LLChat.sendChannel(msg, channel) - leap.send('LLChatBar', {op='sendChat', channel=channel, message=msg}) -end - -- *************************************************************************** -- Group chat -- *************************************************************************** diff --git a/indra/newview/scripts/lua/test_group_chat.lua b/indra/newview/scripts/lua/test_group_chat.lua index 6299ba535f..eaff07ed14 100644 --- a/indra/newview/scripts/lua/test_group_chat.lua +++ b/indra/newview/scripts/lua/test_group_chat.lua @@ -1,5 +1,4 @@ LLChat = require 'LLChat' -inspect = require 'inspect' LLAgent = require 'LLAgent' popup = require 'popup' -- cgit v1.2.3 From b3fb23ee0c6d33f5eba3502328ffb0011b5f25fb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 15 Aug 2024 09:49:34 -0400 Subject: Introduce lluau_checkstack(L, n); use instead of luaL_checkstack(). luaL_checkstack() accepts a third parameter which is included in the stack overflow error message. We've been passing nullptr, leading to messages of the form "stack overflow ((null))". lluau_checkstack() implicitly passes __FUNCTION__, so we can distinguish which underlying luaL_checkstack() call encountered the stack overflow condition. Also, when calling each atexit() function, pass Luau's debug.traceback() function as the lua_pcall() error handler. This should help diagnose errors in atexit() functions. --- indra/llcommon/lua_function.cpp | 57 ++++++++++++++++++++++++++--------------- indra/llcommon/lua_function.h | 13 ++++++---- indra/newview/llluamanager.cpp | 6 ++--- 3 files changed, 47 insertions(+), 29 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index b173d17ede..defadea358 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -127,7 +127,7 @@ std::string lua_tostdstring(lua_State* L, int index) void lua_pushstdstring(lua_State* L, const std::string& str) { - luaL_checkstack(L, 1, nullptr); + lluau_checkstack(L, 1); lua_pushlstring(L, str.c_str(), str.length()); } @@ -240,10 +240,10 @@ LLSD lua_tollsd(lua_State* L, int index) // undefined LLSD. Naturally, though, those won't survive a second // round trip. - // This is the most important of the luaL_checkstack() calls because a + // This is the most important of the lluau_checkstack() calls because a // deeply nested Lua structure will enter this case at each level, and // we'll need another 2 stack slots to traverse each nested table. - luaL_checkstack(L, 2, nullptr); + lluau_checkstack(L, 2); // BEFORE we push nil to initialize the lua_next() traversal, convert // 'index' to absolute! Our caller might have passed a relative index; // we do, below: lua_tollsd(L, -1). If 'index' is -1, then when we @@ -400,7 +400,7 @@ LLSD lua_tollsd(lua_State* L, int index) void lua_pushllsd(lua_State* L, const LLSD& data) { // might need 2 slots for array or map - luaL_checkstack(L, 2, nullptr); + lluau_checkstack(L, 2); switch (data.type()) { case LLSD::TypeUndefined: @@ -487,39 +487,54 @@ LuaState::LuaState(script_finished_fn cb): LuaState::~LuaState() { - // We're just about to destroy this lua_State mState. lua_close() doesn't - // implicitly garbage-collect everything, so (for instance) any lingering - // objects with __gc metadata methods aren't cleaned up. This is why we - // provide atexit(). - luaL_checkstack(mState, 2, nullptr); + // We're just about to destroy this lua_State mState. Did this Lua chunk + // register any atexit() functions? + lluau_checkstack(mState, 3); // look up Registry.atexit lua_getfield(mState, LUA_REGISTRYINDEX, "atexit"); // stack contains Registry.atexit if (lua_istable(mState, -1)) { + // Push debug.traceback() onto the stack as lua_pcall()'s error + // handler function. On error, lua_pcall() calls the specified error + // handler function with the original error message; the message + // returned by the error handler is then returned by lua_pcall(). + // Luau's debug.traceback() is called with a message to prepend to the + // returned traceback string. Almost as if they'd been designed to + // work together... + lua_getglobal(mState, "debug"); + lua_getfield(mState, -1, "traceback"); + // ditch "debug" + lua_remove(mState, -2); + // stack now contains atexit, debug.traceback() + // We happen to know that Registry.atexit is built by appending array // entries using table.insert(). That's important because it means // there are no holes, and therefore lua_objlen() should be correct. // That's important because we walk the atexit table backwards, to // destroy last the things we created (passed to LL.atexit()) first. - for (int i(lua_objlen(mState, -1)); i >= 1; --i) + for (int i(lua_objlen(mState, -2)); i >= 1; --i) { lua_pushinteger(mState, i); - // stack contains Registry.atexit, i - lua_gettable(mState, -2); - // stack contains Registry.atexit, atexit[i] + // stack contains Registry.atexit, debug.traceback(), i + lua_gettable(mState, -3); + // stack contains Registry.atexit, debug.traceback(), atexit[i] // Call atexit[i](), no args, no return values. // Use lua_pcall() because errors in any one atexit() function - // shouldn't cancel the rest of them. - if (lua_pcall(mState, 0, 0, 0) != LUA_OK) + // shouldn't cancel the rest of them. Pass debug.traceback() as + // the error handler function. + if (lua_pcall(mState, 0, 0, -2) != LUA_OK) { auto error{ lua_tostdstring(mState, -1) }; LL_WARNS("Lua") << "atexit() function error: " << error << LL_ENDL; // pop error message lua_pop(mState, 1); } - // lua_pcall() has already popped atexit[i]: stack contains atexit + // lua_pcall() has already popped atexit[i]: + // stack contains atexit, debug.traceback() } + // pop debug.traceback() + lua_pop(mState, 1); } // pop Registry.atexit (either table or nil) lua_pop(mState, 1); @@ -625,7 +640,7 @@ std::pair LuaState::expr(const std::string& desc, const std::string& LuaListener& LuaState::obtainListener(lua_State* L) { - luaL_checkstack(L, 2, nullptr); + lluau_checkstack(L, 2); lua_getfield(L, LUA_REGISTRYINDEX, "LuaListener"); // compare lua_type() because lua_isuserdata() also accepts light userdata if (lua_type(L, -1) != LUA_TUSERDATA) @@ -655,7 +670,7 @@ LuaListener& LuaState::obtainListener(lua_State* L) lua_function(atexit, "atexit(function): " "register Lua function to be called at script termination") { - luaL_checkstack(L, 4, nullptr); + lluau_checkstack(L, 4); // look up the global name "table" lua_getglobal(L, "table"); // stack contains function, table @@ -705,7 +720,7 @@ LuaFunction::LuaFunction(const std::string_view& name, lua_CFunction function, void LuaFunction::init(lua_State* L) { const auto& [registry, lookup] = getRState(); - luaL_checkstack(L, 2, nullptr); + lluau_checkstack(L, 2); // create LL table -- // it happens that we know exactly how many non-array members we want lua_createtable(L, 0, int(narrow(lookup.size()))); @@ -743,7 +758,7 @@ std::pair LuaFunction::getState() *****************************************************************************/ lua_function(source_path, "source_path(): return the source path of the running Lua script") { - luaL_checkstack(L, 1, nullptr); + lluau_checkstack(L, 1); lua_pushstdstring(L, lluau::source_path(L).u8string()); return 1; } @@ -753,7 +768,7 @@ lua_function(source_path, "source_path(): return the source path of the running *****************************************************************************/ lua_function(source_dir, "source_dir(): return the source directory of the running Lua script") { - luaL_checkstack(L, 1, nullptr); + lluau_checkstack(L, 1); lua_pushstdstring(L, lluau::source_path(L).parent_path().u8string()); return 1; } diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 7b59af30f5..c1a4d736a0 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -65,6 +65,9 @@ namespace lluau void check_interrupts_counter(lua_State* L); } // namespace lluau +// must be a macro because __FUNCTION__ is context-sensitive +#define lluau_checkstack(L, n) luaL_checkstack((L), (n), __FUNCTION__) + std::string lua_tostdstring(lua_State* L, int index); void lua_pushstdstring(lua_State* L, const std::string& str); LLSD lua_tollsd(lua_State* L, int index); @@ -294,7 +297,7 @@ auto lua_to(lua_State* L, int index) template auto lua_getfieldv(lua_State* L, int index, const char* k) { - luaL_checkstack(L, 1, nullptr); + lluau_checkstack(L, 1); lua_getfield(L, index, k); LuaPopper pop(L, 1); return lua_to(L, -1); @@ -304,7 +307,7 @@ auto lua_getfieldv(lua_State* L, int index, const char* k) template auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value) { - luaL_checkstack(L, 1, nullptr); + lluau_checkstack(L, 1); lua_push(L, value); lua_setfield(L, index, k); } @@ -313,7 +316,7 @@ auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value) template auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) { - luaL_checkstack(L, 1, nullptr); + lluau_checkstack(L, 1); lua_pushlstring(L, k.data(), k.length()); lua_rawget(L, index); LuaPopper pop(L, 1); @@ -324,7 +327,7 @@ auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) template void lua_rawsetfield(lua_State* L, int index, const std::string_view& k, const T& value) { - luaL_checkstack(L, 2, nullptr); + lluau_checkstack(L, 2); lua_pushlstring(L, k.data(), k.length()); lua_push(L, value); lua_rawset(L, index); @@ -430,7 +433,7 @@ DistinctInt TypeTag::value; template void lua_emplace(lua_State* L, ARGS&&... args) { - luaL_checkstack(L, 1, nullptr); + lluau_checkstack(L, 1); int tag{ TypeTag::value }; if (! lua_getuserdatadtor(L, tag)) { diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 7014c59e4e..0b1a73f4e9 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -66,7 +66,7 @@ std::string lua_print_msg(lua_State* L, const std::string_view& level) { // On top of existing Lua arguments, we're going to push tostring() and // duplicate each existing stack entry so we can stringize each one. - luaL_checkstack(L, 2, nullptr); + lluau_checkstack(L, 2); luaL_where(L, 1); // start with the 'where' info at the top of the stack std::ostringstream out; @@ -139,7 +139,7 @@ lua_function(get_event_pumps, "Events posted to replypump are queued for get_event_next().\n" "post_on(commandpump, ...) to engage LLEventAPI operations (see helpleap()).") { - luaL_checkstack(L, 2, nullptr); + lluau_checkstack(L, 2); auto& listener{ LuaState::obtainListener(L) }; // return the reply pump name and the command pump name on caller's lua_State lua_pushstdstring(L, listener.getReplyName()); @@ -153,7 +153,7 @@ lua_function(get_event_next, "is returned by get_event_pumps(). Blocks the calling chunk until an\n" "event becomes available.") { - luaL_checkstack(L, 2, nullptr); + lluau_checkstack(L, 2); auto& listener{ LuaState::obtainListener(L) }; const auto& [pump, data]{ listener.getNext() }; lua_pushstdstring(L, pump); -- cgit v1.2.3 From ab0f7ff14cd80b89524ba95eb5a39e2d6df55b26 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Thu, 15 Aug 2024 23:29:54 +0300 Subject: First batch of Inventory api; raise interrupts limit --- indra/llcommon/lua_function.cpp | 2 +- indra/llinventory/llfoldertype.cpp | 17 +++ indra/llinventory/llfoldertype.h | 2 + indra/newview/CMakeLists.txt | 2 + indra/newview/llinventorylistener.cpp | 130 ++++++++++++++++++++++ indra/newview/llinventorylistener.h | 45 ++++++++ indra/newview/llviewerinventory.cpp | 3 + indra/newview/scripts/lua/require/LLInventory.lua | 28 +++++ 8 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 indra/newview/llinventorylistener.cpp create mode 100644 indra/newview/llinventorylistener.h create mode 100644 indra/newview/scripts/lua/require/LLInventory.lua (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index b173d17ede..59aa2a0ba7 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -34,7 +34,7 @@ using namespace std::literals; // e.g. std::string_view literals: "this"sv -const S32 INTERRUPTS_MAX_LIMIT = 20000; +const S32 INTERRUPTS_MAX_LIMIT = 100000; const S32 INTERRUPTS_SUSPEND_LIMIT = 100; #define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n))) diff --git a/indra/llinventory/llfoldertype.cpp b/indra/llinventory/llfoldertype.cpp index 8f968ae2fd..158759c957 100644 --- a/indra/llinventory/llfoldertype.cpp +++ b/indra/llinventory/llfoldertype.cpp @@ -29,6 +29,7 @@ #include "llfoldertype.h" #include "lldictionary.h" #include "llmemory.h" +#include "llsd.h" #include "llsingleton.h" ///---------------------------------------------------------------------------- @@ -220,3 +221,19 @@ const std::string &LLFolderType::badLookup() static const std::string sBadLookup = "llfoldertype_bad_lookup"; return sBadLookup; } + +LLSD LLFolderType::getTypeNames() +{ + LLSD type_names; + for (S32 type = FT_TEXTURE; type < FT_COUNT; ++type) + { + if (lookupIsEnsembleType((LLFolderType::EType)type)) continue; + + const FolderEntry *entry = LLFolderDictionary::getInstance()->lookup((LLFolderType::EType)type); + if (entry) + { + type_names.append(entry->mName); + } + } + return type_names; +} diff --git a/indra/llinventory/llfoldertype.h b/indra/llinventory/llfoldertype.h index 46a1b92a96..dd12693f66 100644 --- a/indra/llinventory/llfoldertype.h +++ b/indra/llinventory/llfoldertype.h @@ -115,6 +115,8 @@ public: static const std::string& badLookup(); // error string when a lookup fails + static LLSD getTypeNames(); + protected: LLFolderType() {} ~LLFolderType() {} diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 4e7e072289..04eb2c1d6d 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -360,6 +360,7 @@ set(viewer_SOURCE_FILES llinventorygallerymenu.cpp llinventoryicon.cpp llinventoryitemslist.cpp + llinventorylistener.cpp llinventorylistitem.cpp llinventorymodel.cpp llinventorymodelbackgroundfetch.cpp @@ -1027,6 +1028,7 @@ set(viewer_HEADER_FILES llinventorygallerymenu.h llinventoryicon.h llinventoryitemslist.h + llinventorylistener.h llinventorylistitem.h llinventorymodel.h llinventorymodelbackgroundfetch.h diff --git a/indra/newview/llinventorylistener.cpp b/indra/newview/llinventorylistener.cpp new file mode 100644 index 0000000000..a8269bb80d --- /dev/null +++ b/indra/newview/llinventorylistener.cpp @@ -0,0 +1,130 @@ +/** + * @file llinventorylistener.cpp + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llinventorylistener.h" + +#include "llappearancemgr.h" +#include "llinventoryfunctions.h" +#include "lltransutil.h" +#include "llwearableitemslist.h" +#include "stringize.h" + +LLInventoryListener::LLInventoryListener() + : LLEventAPI("LLInventory", + "API for interactions with viewer Inventory items") +{ + add("getItemsInfo", + "Return information about items or folders defined in [\"items_id\"]:\n" + "reply will contain [\"items\"] and [\"categories\"] tables accordingly", + &LLInventoryListener::getItemsInfo, + llsd::map("items_id", LLSD(), "reply", LLSD())); + + add("getFolderTypeNames", + "Return the table of folder type names, contained in [\"type_names\"]\n", + &LLInventoryListener::getFolderTypeNames, + llsd::map("reply", LLSD())); + + add("getBasicFolderID", + "Return the UUID of the folder by specified folder type name, for example:\n" + "\"Textures\", \"My outfits\", \"Sounds\" and other basic folders which have associated type", + &LLInventoryListener::getBasicFolderID, + llsd::map("ft_name", LLSD(), "reply", LLSD())); + + add("getDirectDescendents", + "Return the direct descendents(both items and folders) of the [\"folder_id\"]", + &LLInventoryListener::getDirectDescendents, + llsd::map("folder_id", LLSD(), "reply", LLSD())); + } + + +void add_item_info(LLEventAPI::Response& response, LLViewerInventoryItem* item) +{ + response["items"].insert(item->getUUID().asString(), + llsd::map("name", item->getName(), "parent_id", item->getParentUUID(), "desc", item->getDescription(), + "inv_type", LLInventoryType::lookup(item->getInventoryType()), "creation_date", + (S32) item->getCreationDate(), "asset_id", item->getAssetUUID(), "is_link", item->getIsLinkType(), + "linked_id", item->getLinkedUUID())); +} + +void add_cat_info(LLEventAPI::Response &response, LLViewerInventoryCategory *cat) +{ + response["categories"].insert(cat->getUUID().asString(), + llsd::map("name", cat->getName(), "parent_id", cat->getParentUUID(), "type", + LLFolderType::lookup(cat->getPreferredType()), "creation_date", (S32) cat->getCreationDate(), + "is_link", cat->getIsLinkType(), "linked_id", cat->getLinkedUUID())); +} + +void LLInventoryListener::getItemsInfo(LLSD const &data) +{ + Response response(LLSD(), data); + + uuid_vec_t ids = LLSDParam(data["items_id"]); + for (auto &it : ids) + { + LLViewerInventoryItem* item = gInventory.getItem(it); + if (item) + { + add_item_info(response, item); + } + LLViewerInventoryCategory* cat = gInventory.getCategory(it); + if (cat) + { + add_cat_info(response, cat); + } + } +} + +void LLInventoryListener::getFolderTypeNames(LLSD const &data) +{ + Response response(llsd::map("type_names", LLFolderType::getTypeNames()), data); +} + +void LLInventoryListener::getBasicFolderID(LLSD const &data) +{ + Response response(llsd::map("id", gInventory.findCategoryUUIDForType(LLFolderType::lookup(data["ft_name"].asString()))), data); +} + + +void LLInventoryListener::getDirectDescendents(LLSD const &data) +{ + Response response(LLSD(), data); + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(data["folder_id"], cats, items); + + LLInventoryModel::item_array_t items_copy = *items; + for (LLInventoryModel::item_array_t::iterator iter = items_copy.begin(); iter != items_copy.end(); iter++) + { + add_item_info(response, *iter); + } + LLInventoryModel::cat_array_t cats_copy = *cats; + for (LLInventoryModel::cat_array_t::iterator iter = cats_copy.begin(); iter != cats_copy.end(); iter++) + { + add_cat_info(response, *iter); + } +} + diff --git a/indra/newview/llinventorylistener.h b/indra/newview/llinventorylistener.h new file mode 100644 index 0000000000..e148df48fe --- /dev/null +++ b/indra/newview/llinventorylistener.h @@ -0,0 +1,45 @@ +/** + * @file llinventorylistener.h + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#ifndef LL_LLINVENTORYLISTENER_H +#define LL_LLINVENTORYLISTENER_H + +#include "lleventapi.h" + +class LLInventoryListener : public LLEventAPI +{ +public: + LLInventoryListener(); + +private: + void getItemsInfo(LLSD const &data); + void getFolderTypeNames(LLSD const &data); + void getBasicFolderID(LLSD const &data); + void getDirectDescendents(LLSD const &data); +}; + +#endif // LL_LLINVENTORYLISTENER_H + diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 16810efa01..7030426e21 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -71,6 +71,9 @@ #include "llclipboard.h" #include "llhttpretrypolicy.h" #include "llsettingsvo.h" +#include "llinventorylistener.h" + +LLInventoryListener sInventoryListener; // do-nothing ops for use in callbacks. void no_op_inventory_func(const LLUUID&) {} diff --git a/indra/newview/scripts/lua/require/LLInventory.lua b/indra/newview/scripts/lua/require/LLInventory.lua new file mode 100644 index 0000000000..880a2516f1 --- /dev/null +++ b/indra/newview/scripts/lua/require/LLInventory.lua @@ -0,0 +1,28 @@ +local leap = require 'leap' +local mapargs = require 'mapargs' + +local LLInventory = {} + +-- Get the items/folders info by provided IDs, +-- reply will contain "items" and "categories" tables accordingly +function LLInventory.getItemsInfo(items_id) + return leap.request('LLInventory', {op = 'getItemsInfo', items_id=items_id}) +end + +-- Get the table of folder type names, which can be later used to get the ID of the basic folders +function LLInventory.getFolderTypeNames() + return leap.request('LLInventory', {op = 'getFolderTypeNames'}).type_names +end + +-- Get the UUID of the basic folder("Textures", "My outfits", "Sounds" etc.) by specified folder type name +function LLInventory.getBasicFolderID(ft_name) + return leap.request('LLInventory', {op = 'getBasicFolderID', ft_name=ft_name}).id +end + +-- Get the direct descendents of the 'folder_id' provided, +-- reply will contain "items" and "categories" tables accordingly +function LLInventory.getDirectDescendents(folder_id) + return leap.request('LLInventory', {op = 'getDirectDescendents', folder_id=folder_id}) +end + +return LLInventory -- cgit v1.2.3 From 3cef79d979f2d21c29d17d123d6c166b9f7e7e0e Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Tue, 20 Aug 2024 23:09:14 +0300 Subject: Add collectDescendentsIf api for Lua --- indra/llcommon/llassettype.cpp | 17 +++ indra/llcommon/llassettype.h | 2 + indra/llinventory/llfoldertype.cpp | 1 + indra/newview/llinventorylistener.cpp | 140 +++++++++++++++++++++- indra/newview/llinventorylistener.h | 27 +++++ indra/newview/scripts/lua/require/LLInventory.lua | 19 +++ 6 files changed, 200 insertions(+), 6 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp index 3e46bde954..1c0893b1bf 100644 --- a/indra/llcommon/llassettype.cpp +++ b/indra/llcommon/llassettype.cpp @@ -30,6 +30,7 @@ #include "lldictionary.h" #include "llmemory.h" #include "llsingleton.h" +#include "llsd.h" ///---------------------------------------------------------------------------- /// Class LLAssetType @@ -244,3 +245,19 @@ bool LLAssetType::lookupIsAssetIDKnowable(EType asset_type) } return false; } + +LLSD LLAssetType::getTypeNames() +{ + LLSD type_names; + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + for (S32 type = AT_TEXTURE; type < AT_COUNT; ++type) + { + const AssetEntry *entry = dict->lookup((LLAssetType::EType) type); + // skip llassettype_bad_lookup + if (entry) + { + type_names.append(entry->mTypeName); + } + } + return type_names; +} diff --git a/indra/llcommon/llassettype.h b/indra/llcommon/llassettype.h index 1989155550..4185063ae5 100644 --- a/indra/llcommon/llassettype.h +++ b/indra/llcommon/llassettype.h @@ -163,6 +163,8 @@ public: static bool lookupIsAssetFetchByIDAllowed(EType asset_type); // the asset allows direct download static bool lookupIsAssetIDKnowable(EType asset_type); // asset data can be known by the viewer + static LLSD getTypeNames(); + static const std::string BADLOOKUP; protected: diff --git a/indra/llinventory/llfoldertype.cpp b/indra/llinventory/llfoldertype.cpp index 158759c957..c8ef1ccc1b 100644 --- a/indra/llinventory/llfoldertype.cpp +++ b/indra/llinventory/llfoldertype.cpp @@ -230,6 +230,7 @@ LLSD LLFolderType::getTypeNames() if (lookupIsEnsembleType((LLFolderType::EType)type)) continue; const FolderEntry *entry = LLFolderDictionary::getInstance()->lookup((LLFolderType::EType)type); + //skip llfoldertype_bad_lookup if (entry) { type_names.append(entry->mName); diff --git a/indra/newview/llinventorylistener.cpp b/indra/newview/llinventorylistener.cpp index a8269bb80d..4beb59c58e 100644 --- a/indra/newview/llinventorylistener.cpp +++ b/indra/newview/llinventorylistener.cpp @@ -48,6 +48,11 @@ LLInventoryListener::LLInventoryListener() &LLInventoryListener::getFolderTypeNames, llsd::map("reply", LLSD())); + add("getAssetTypeNames", + "Return the table of asset type names, contained in [\"type_names\"]\n", + &LLInventoryListener::getAssetTypeNames, + llsd::map("reply", LLSD())); + add("getBasicFolderID", "Return the UUID of the folder by specified folder type name, for example:\n" "\"Textures\", \"My outfits\", \"Sounds\" and other basic folders which have associated type", @@ -58,6 +63,15 @@ LLInventoryListener::LLInventoryListener() "Return the direct descendents(both items and folders) of the [\"folder_id\"]", &LLInventoryListener::getDirectDescendents, llsd::map("folder_id", LLSD(), "reply", LLSD())); + + add("collectDescendentsIf", + "Return the descendents(both items and folders) of the [\"folder_id\"], if it passes specified filters:\n" + "[\"name\"] is a substring of object's name,\n" + "[\"desc\"] is a substring of object's description,\n" + "asset [\"type\"] corresponds to the object's asset type\n" + "[\"filter_links\"]: EXCLUDE_LINKS - don't show links, ONLY_LINKS - only show links, INCLUDE_LINKS - show links too (default)", + &LLInventoryListener::collectDescendentsIf, + llsd::map("folder_id", LLSD(), "reply", LLSD())); } @@ -65,17 +79,15 @@ void add_item_info(LLEventAPI::Response& response, LLViewerInventoryItem* item) { response["items"].insert(item->getUUID().asString(), llsd::map("name", item->getName(), "parent_id", item->getParentUUID(), "desc", item->getDescription(), - "inv_type", LLInventoryType::lookup(item->getInventoryType()), "creation_date", - (S32) item->getCreationDate(), "asset_id", item->getAssetUUID(), "is_link", item->getIsLinkType(), - "linked_id", item->getLinkedUUID())); + "inv_type", LLInventoryType::lookup(item->getInventoryType()), "asset_type", LLAssetType::lookup(item->getType()), + "creation_date", (S32) item->getCreationDate(), "asset_id", item->getAssetUUID(), + "is_link", item->getIsLinkType(), "linked_id", item->getLinkedUUID())); } void add_cat_info(LLEventAPI::Response &response, LLViewerInventoryCategory *cat) { response["categories"].insert(cat->getUUID().asString(), - llsd::map("name", cat->getName(), "parent_id", cat->getParentUUID(), "type", - LLFolderType::lookup(cat->getPreferredType()), "creation_date", (S32) cat->getCreationDate(), - "is_link", cat->getIsLinkType(), "linked_id", cat->getLinkedUUID())); + llsd::map("name", cat->getName(), "parent_id", cat->getParentUUID(), "type", LLFolderType::lookup(cat->getPreferredType()))); } void LLInventoryListener::getItemsInfo(LLSD const &data) @@ -103,6 +115,11 @@ void LLInventoryListener::getFolderTypeNames(LLSD const &data) Response response(llsd::map("type_names", LLFolderType::getTypeNames()), data); } +void LLInventoryListener::getAssetTypeNames(LLSD const &data) +{ + Response response(llsd::map("type_names", LLAssetType::getTypeNames()), data); +} + void LLInventoryListener::getBasicFolderID(LLSD const &data) { Response response(llsd::map("id", gInventory.findCategoryUUIDForType(LLFolderType::lookup(data["ft_name"].asString()))), data); @@ -128,3 +145,114 @@ void LLInventoryListener::getDirectDescendents(LLSD const &data) } } +void LLInventoryListener::collectDescendentsIf(LLSD const &data) +{ + Response response(LLSD(), data); + LLUUID folder_id(data["folder_id"].asUUID()); + LLViewerInventoryCategory *cat = gInventory.getCategory(folder_id); + if (!cat) + { + return response.error(stringize("Folder ", std::quoted(data["folder_id"].asString()), " was not found")); + } + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + + LLFilteredCollector collector = LLFilteredCollector(data); + + gInventory.collectDescendentsIf(folder_id, cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH, collector); + + for (LLInventoryModel::item_array_t::iterator iter = item_array.begin(); iter != item_array.end(); iter++) + { + add_item_info(response, *iter); + } + for (LLInventoryModel::cat_array_t::iterator iter = cat_array.begin(); iter != cat_array.end(); iter++) + { + add_cat_info(response, *iter); + } +} + +LLFilteredCollector::LLFilteredCollector(LLSD const &data) + : mType(LLAssetType::EType::AT_UNKNOWN), + mLinkFilter(INCLUDE_LINKS) +{ + if (data.has("name")) + { + mName = data["name"]; + } + if (data.has("desc")) + { + mDesc = data["desc"]; + } + if (data.has("type")) + { + mType = LLAssetType::lookup(data["type"]); + } + if (data.has("filter_links")) + { + if (data["filter_links"] == "EXCLUDE_LINKS") + { + mLinkFilter = EXCLUDE_LINKS; + } + else if (data["filter_links"] == "ONLY_LINKS") + { + mLinkFilter = ONLY_LINKS; + } + } +} + +bool LLFilteredCollector::operator()(LLInventoryCategory *cat, LLInventoryItem *item) +{ + bool passed = checkagainstType(cat, item); + passed = passed && checkagainstNameDesc(cat, item); + passed = passed && checkagainstLinks(cat, item); + + return passed; +} + +bool LLFilteredCollector::checkagainstNameDesc(LLInventoryCategory *cat, LLInventoryItem *item) +{ + std::string name, desc; + bool passed(true); + if (cat) + { + if (!mDesc.empty()) return false; + name = cat->getName(); + } + if (item) + { + name = item->getName(); + passed = (mDesc.size() ? item->getDescription().find(mDesc) != std::string::npos : true); + } + + return passed && (mName.size() ? name.find(mName) != std::string::npos : true); +} + +bool LLFilteredCollector::checkagainstType(LLInventoryCategory *cat, LLInventoryItem *item) +{ + if (mType == LLAssetType::AT_UNKNOWN) + { + return true; + } + if (mType == LLAssetType::AT_CATEGORY) + { + if (cat) + { + return true; + } + } + if (item && item->getType() == mType) + { + return true; + } + return false; +} + +bool LLFilteredCollector::checkagainstLinks(LLInventoryCategory *cat, LLInventoryItem *item) +{ + bool is_link = cat ? cat->getIsLinkType() : item->getIsLinkType(); + if (is_link && (mLinkFilter == EXCLUDE_LINKS)) + return false; + if (!is_link && (mLinkFilter == ONLY_LINKS)) + return false; + return true; +} diff --git a/indra/newview/llinventorylistener.h b/indra/newview/llinventorylistener.h index e148df48fe..83e1751e1b 100644 --- a/indra/newview/llinventorylistener.h +++ b/indra/newview/llinventorylistener.h @@ -28,6 +28,7 @@ #define LL_LLINVENTORYLISTENER_H #include "lleventapi.h" +#include "llinventoryfunctions.h" class LLInventoryListener : public LLEventAPI { @@ -37,8 +38,34 @@ public: private: void getItemsInfo(LLSD const &data); void getFolderTypeNames(LLSD const &data); + void getAssetTypeNames(LLSD const &data); void getBasicFolderID(LLSD const &data); void getDirectDescendents(LLSD const &data); + void collectDescendentsIf(LLSD const &data); +}; + +struct LLFilteredCollector : public LLInventoryCollectFunctor +{ + enum EFilterLink + { + INCLUDE_LINKS, // show links too + EXCLUDE_LINKS, // don't show links + ONLY_LINKS // only show links + }; + + LLFilteredCollector(LLSD const &data); + virtual ~LLFilteredCollector() {} + virtual bool operator()(LLInventoryCategory *cat, LLInventoryItem *item); + + protected: + bool checkagainstType(LLInventoryCategory *cat, LLInventoryItem *item); + bool checkagainstNameDesc(LLInventoryCategory *cat, LLInventoryItem *item); + bool checkagainstLinks(LLInventoryCategory *cat, LLInventoryItem *item); + + LLAssetType::EType mType; + std::string mName; + std::string mDesc; + EFilterLink mLinkFilter; }; #endif // LL_LLINVENTORYLISTENER_H diff --git a/indra/newview/scripts/lua/require/LLInventory.lua b/indra/newview/scripts/lua/require/LLInventory.lua index 880a2516f1..e6a347532b 100644 --- a/indra/newview/scripts/lua/require/LLInventory.lua +++ b/indra/newview/scripts/lua/require/LLInventory.lua @@ -19,10 +19,29 @@ function LLInventory.getBasicFolderID(ft_name) return leap.request('LLInventory', {op = 'getBasicFolderID', ft_name=ft_name}).id end +-- Get the table of asset type names, which can be later used to get the specific items via LLInventory.collectDescendentsIf(...) +function LLInventory.getAssetTypeNames() + return leap.request('LLInventory', {op = 'getAssetTypeNames'}).type_names +end + -- Get the direct descendents of the 'folder_id' provided, -- reply will contain "items" and "categories" tables accordingly function LLInventory.getDirectDescendents(folder_id) return leap.request('LLInventory', {op = 'getDirectDescendents', folder_id=folder_id}) end +-- Get the descendents of the 'folder_id' provided, which pass specified filters +-- reply will contain "items" and "categories" tables accordingly +-- LLInventory.collectDescendentsIf{ folder_id -- parent folder ID +-- [, name] -- name (substring) +-- [, desc] -- description (substring) +-- [, type] -- asset type +-- [, filter_links]} -- EXCLUDE_LINKS - don't show links, ONLY_LINKS - only show links, INCLUDE_LINKS - show links too (default) +function LLInventory.collectDescendentsIf(...) + local args = mapargs('folder_id,name,desc,type,filter_links', ...) + args.op = 'collectDescendentsIf' + return leap.request('LLInventory', args) +end + + return LLInventory -- cgit v1.2.3 From a5337adf79b6a49166a15836ca2822adbb50d8c3 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 20 Aug 2024 19:38:10 -0400 Subject: Add LL::scope_exit --- indra/llcommon/scope_exit.h | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 indra/llcommon/scope_exit.h (limited to 'indra') diff --git a/indra/llcommon/scope_exit.h b/indra/llcommon/scope_exit.h new file mode 100644 index 0000000000..00fab069c4 --- /dev/null +++ b/indra/llcommon/scope_exit.h @@ -0,0 +1,34 @@ +/** + * @file scope_exit.h + * @author Nat Goodspeed + * @date 2024-08-15 + * @brief Cheap imitation of std::experimental::scope_exit + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_SCOPE_EXIT_H) +#define LL_SCOPE_EXIT_H + +#include + +namespace LL +{ + +class scope_exit +{ +public: + scope_exit(const std::function& func): mFunc(func) {} + scope_exit(const scope_exit&) = delete; + scope_exit& operator=(const scope_exit&) = delete; + ~scope_exit() { mFunc(); } + +private: + std::function mFunc; +}; + +} // namespace LL + +#endif /* ! defined(LL_SCOPE_EXIT_H) */ -- cgit v1.2.3 From 68313aa2defcc7d6e5ebbd96344d78f3a31fdb9a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 20 Aug 2024 20:55:48 -0400 Subject: Fix TempSet to use type VAR to store mOldValue. In fact we set mOldValue from mVar, and restore mVar from mOldValue, so the VAR type makes the most sense. The previous way, you'd get actual errors if you tried to use TempSet(pointervar, nullptr): that declared mOldValue to be nullptr_t, which you can't initialize from mVar. --- indra/llcommon/tempset.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/tempset.h b/indra/llcommon/tempset.h index e1496bd5fc..07e607a576 100755 --- a/indra/llcommon/tempset.h +++ b/indra/llcommon/tempset.h @@ -35,7 +35,7 @@ public: private: VAR& mVar; - VALUE mOldValue; + VAR mOldValue; }; #endif /* ! defined(LL_TEMPSET_H) */ -- cgit v1.2.3 From 376c890c095fbc59b83402255cc1036c411150b9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 20 Aug 2024 21:12:25 -0400 Subject: Fix for #2237: intermittent Lua data stack overflow. Use a static unordered_map to allow a function receiving (lua_State* L) to look up the LuaState instance managing that lua_State. We've thought about this from time to time already. LuaState's constructor creates the map entry; its destructor removes it; the new static getParent(lua_State* L) method performs the lookup. Migrate lluau::set_interrupts_counter() and check_interrupts_counter() into LuaState member functions. Add a new mInterrupts counter for them. Importantly, LuaState::check_interrupts_counter(), which is indirectly called by a lua_callbacks().interrupt function, no longer performs any Lua stack operations. Empirically, it seems the Lua engine is capable of interrupting itself at a moment when re-entry confuses it. Change previous lluau::set_interrupts_counter(L, 0) calls to LuaState::getParent(L).set_interrupts_counter(0). Also add LuaStackDelta class, and a lua_checkdelta() helper macro, to verify that the Lua data stack depth on exit from a block differs from the depth on entry by exactly the expected amount. Sprinkle lua_checkdelta() macros in likely places. --- indra/llcommon/lua_function.cpp | 126 +++++++++++++++++++++++++++++++--------- indra/llcommon/lua_function.h | 47 +++++++++++++-- indra/newview/llluamanager.cpp | 5 ++ 3 files changed, 146 insertions(+), 32 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index defadea358..ad77a1e040 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -21,6 +21,7 @@ #include #include // std::unique_ptr #include +#include // external library headers // other Linden headers #include "fsyspath.h" @@ -54,7 +55,10 @@ namespace }; } // anonymous namespace -int lluau::dostring(lua_State* L, const std::string& desc, const std::string& text) +namespace lluau +{ + +int dostring(lua_State* L, const std::string& desc, const std::string& text) { auto r = loadstring(L, desc, text); if (r != LUA_OK) @@ -66,7 +70,7 @@ int lluau::dostring(lua_State* L, const std::string& desc, const std::string& te return lua_pcall(L, 0, LUA_MULTRET, 0); } -int lluau::loadstring(lua_State *L, const std::string &desc, const std::string &text) +int loadstring(lua_State *L, const std::string &desc, const std::string &text) { size_t bytecodeSize = 0; // The char* returned by luau_compile() must be freed by calling free(). @@ -76,7 +80,7 @@ int lluau::loadstring(lua_State *L, const std::string &desc, const std::string & return luau_load(L, desc.data(), bytecode.get(), bytecodeSize, 0); } -fsyspath lluau::source_path(lua_State* L) +fsyspath source_path(lua_State* L) { //Luau lua_Debug and lua_getinfo() are different compared to default Lua: //see https://github.com/luau-lang/luau/blob/80928acb92d1e4b6db16bada6d21b1fb6fa66265/VM/include/lua.h @@ -93,33 +97,14 @@ fsyspath lluau::source_path(lua_State* L) return ar.source; } -void lluau::set_interrupts_counter(lua_State *L, S32 counter) -{ - lua_rawsetfield(L, LUA_REGISTRYINDEX, "_INTERRUPTS"sv, lua_Integer(counter)); -} - -void lluau::check_interrupts_counter(lua_State* L) -{ - auto counter = lua_rawgetfield(L, LUA_REGISTRYINDEX, "_INTERRUPTS"sv); - - set_interrupts_counter(L, ++counter); - if (counter > INTERRUPTS_MAX_LIMIT) - { - lluau::error(L, "Possible infinite loop, terminated."); - } - else if (counter % INTERRUPTS_SUSPEND_LIMIT == 0) - { - LL_DEBUGS("Lua") << LLCoros::getName() << " suspending at " << counter << " interrupts" - << LL_ENDL; - llcoro::suspend(); - } -} +} // namespace lluau /***************************************************************************** * Lua <=> C++ conversions *****************************************************************************/ std::string lua_tostdstring(lua_State* L, int index) { + lua_checkdelta(L); size_t len; const char* strval{ lua_tolstring(L, index, &len) }; return { strval, len }; @@ -127,6 +112,7 @@ std::string lua_tostdstring(lua_State* L, int index) void lua_pushstdstring(lua_State* L, const std::string& str) { + lua_checkdelta(L, 1); lluau_checkstack(L, 1); lua_pushlstring(L, str.c_str(), str.length()); } @@ -148,6 +134,7 @@ 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) { + lua_checkdelta(L); switch (lua_type(L, index)) { case LUA_TNONE: @@ -399,6 +386,7 @@ LLSD lua_tollsd(lua_State* L, int index) // stack a Lua object corresponding to the passed LLSD object. void lua_pushllsd(lua_State* L, const LLSD& data) { + lua_checkdelta(L, 1); // might need 2 slots for array or map lluau_checkstack(L, 2); switch (data.type()) @@ -471,10 +459,23 @@ void lua_pushllsd(lua_State* L, const LLSD& data) /***************************************************************************** * LuaState class *****************************************************************************/ +namespace +{ + +// If we find we're running Lua scripts from more than one thread, sLuaStateMap +// should be thread_local. Until then, avoid the overhead. +using LuaStateMap = std::unordered_map; +static LuaStateMap sLuaStateMap; + +} // anonymous namespace + LuaState::LuaState(script_finished_fn cb): mCallback(cb), mState(luaL_newstate()) { + // Ensure that we can always find this LuaState instance, given the + // lua_State we just created or any of its coroutines. + sLuaStateMap.emplace(mState, this); luaL_openlibs(mState); // publish to this new lua_State all the LL entry points we defined using // the lua_function() macro @@ -545,7 +546,9 @@ LuaState::~LuaState() { // mError potentially set by previous checkLua() call(s) mCallback(mError); - } + } + // with the demise of this LuaState, remove sLuaStateMap entry + sLuaStateMap.erase(mState); } bool LuaState::checkLua(const std::string& desc, int r) @@ -563,7 +566,7 @@ bool LuaState::checkLua(const std::string& desc, int r) std::pair LuaState::expr(const std::string& desc, const std::string& text) { - lluau::set_interrupts_counter(mState, 0); + set_interrupts_counter(0); lua_callbacks(mState)->interrupt = [](lua_State *L, int gc) { @@ -572,7 +575,7 @@ std::pair LuaState::expr(const std::string& desc, const std::string& return; LLCoros::checkStop(); - lluau::check_interrupts_counter(L); + LuaState::getParent(L).check_interrupts_counter(); }; LL_INFOS("Lua") << desc << " run" << LL_ENDL; @@ -664,12 +667,53 @@ LuaListener& LuaState::obtainListener(lua_State* L) return *listener; } +LuaState& LuaState::getParent(lua_State* L) +{ + // Look up the LuaState instance associated with the *script*, not the + // specific Lua *coroutine*. In other words, first find this lua_State's + // main thread. + auto found{ sLuaStateMap.find(lua_mainthread(L)) }; + // Our constructor creates the map entry, our destructor deletes it. As + // long as the LuaState exists, we should be able to find it. And we + // SHOULD only be talking to a lua_State managed by a LuaState instance. + llassert(found != sLuaStateMap.end()); + return *found->second; +} + +void LuaState::set_interrupts_counter(S32 counter) +{ + mInterrupts = counter; +} + +void LuaState::check_interrupts_counter() +{ + // The official way to manage data associated with a lua_State is to store + // it *as* Lua data within the lua_State. But this method is called by the + // Lua engine via lua_callbacks(L)->interrupt, and empirically we've hit + // mysterious Lua data stack overflows trying to use stack-based Lua data + // access functions in that situation. It seems the Lua engine is capable + // of interrupting itself at a moment when re-entry is not valid. So only + // touch data in this LuaState. + ++mInterrupts; + if (mInterrupts > INTERRUPTS_MAX_LIMIT) + { + lluau::error(mState, "Possible infinite loop, terminated."); + } + else if (mInterrupts % INTERRUPTS_SUSPEND_LIMIT == 0) + { + LL_DEBUGS("Lua") << LLCoros::getName() << " suspending at " << mInterrupts + << " interrupts" << LL_ENDL; + llcoro::suspend(); + } +} + /***************************************************************************** * atexit() *****************************************************************************/ lua_function(atexit, "atexit(function): " "register Lua function to be called at script termination") { + lua_checkdelta(L, -1); lluau_checkstack(L, 4); // look up the global name "table" lua_getglobal(L, "table"); @@ -758,6 +802,7 @@ std::pair LuaFunction::getState() *****************************************************************************/ lua_function(source_path, "source_path(): return the source path of the running Lua script") { + lua_checkdelta(L, 1); lluau_checkstack(L, 1); lua_pushstdstring(L, lluau::source_path(L).u8string()); return 1; @@ -768,6 +813,7 @@ lua_function(source_path, "source_path(): return the source path of the running *****************************************************************************/ lua_function(source_dir, "source_dir(): return the source directory of the running Lua script") { + lua_checkdelta(L, 1); lluau_checkstack(L, 1); lua_pushstdstring(L, lluau::source_path(L).parent_path().u8string()); return 1; @@ -779,6 +825,7 @@ lua_function(source_dir, "source_dir(): return the source directory of the runni lua_function(abspath, "abspath(path): " "for given filesystem path relative to running script, return absolute path") { + lua_checkdelta(L); auto path{ lua_tostdstring(L, 1) }; lua_pop(L, 1); lua_pushstdstring(L, (lluau::source_path(L).parent_path() / path).u8string()); @@ -790,6 +837,7 @@ lua_function(abspath, "abspath(path): " *****************************************************************************/ lua_function(check_stop, "check_stop(): ensure that a Lua script responds to viewer shutdown") { + lua_checkdelta(L); LLCoros::checkStop(); return 0; } @@ -994,3 +1042,27 @@ std::ostream& operator<<(std::ostream& out, const lua_stack& self) out << ']'; return out; } + +/***************************************************************************** +* LuaStackDelta +*****************************************************************************/ +LuaStackDelta::LuaStackDelta(lua_State* L, const std::string& where, int delta): + L(L), + mWhere(where), + mDepth(lua_gettop(L)), + mDelta(delta) +{} + +LuaStackDelta::~LuaStackDelta() +{ + auto depth{ lua_gettop(L) }; + if (mDepth + mDelta != depth) + { + LL_ERRS("Lua") << mWhere << ": Lua stack went from " << mDepth << " to " << depth; + if (mDelta) + { + LL_CONT << ", rather than expected " << (mDepth + mDelta) << " (" << mDelta << ")"; + } + LL_ENDL; + } +} diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index c1a4d736a0..6965e206ab 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -60,13 +60,10 @@ namespace lluau int loadstring(lua_State* L, const std::string& desc, const std::string& text); fsyspath source_path(lua_State* L); - - void set_interrupts_counter(lua_State *L, S32 counter); - void check_interrupts_counter(lua_State* L); } // namespace lluau -// must be a macro because __FUNCTION__ is context-sensitive -#define lluau_checkstack(L, n) luaL_checkstack((L), (n), __FUNCTION__) +// must be a macro because LL_PRETTY_FUNCTION is context-sensitive +#define lluau_checkstack(L, n) luaL_checkstack((L), (n), LL_PRETTY_FUNCTION) std::string lua_tostdstring(lua_State* L, int index); void lua_pushstdstring(lua_State* L, const std::string& str); @@ -110,10 +107,18 @@ public: // Find or create LuaListener for passed lua_State. static LuaListener& obtainListener(lua_State* L); + // Given lua_State* L, return the LuaState object managing (the main Lua + // thread for) L. + static LuaState& getParent(lua_State* L); + + void set_interrupts_counter(S32 counter); + void check_interrupts_counter(); + private: script_finished_fn mCallback; lua_State* mState; std::string mError; + S32 mInterrupts{ 0 }; }; /***************************************************************************** @@ -172,6 +177,32 @@ private: int mIndex; }; +/***************************************************************************** +* LuaStackDelta +*****************************************************************************/ +/** + * Instantiate LuaStackDelta in a block to compare the Lua data stack depth on + * entry (LuaStackDelta construction) and exit. Optionally, pass the expected + * depth increment. (But be aware that LuaStackDelta cannot observe the effect + * of a LuaPopper or LuaRemover declared previously in the same block.) + */ +class LuaStackDelta +{ +public: + LuaStackDelta(lua_State* L, const std::string& where, int delta=0); + LuaStackDelta(const LuaStackDelta&) = delete; + LuaStackDelta& operator=(const LuaStackDelta&) = delete; + + ~LuaStackDelta(); + +private: + lua_State* L; + std::string mWhere; + int mDepth, mDelta; +}; + +#define lua_checkdelta(L, ...) LuaStackDelta delta(L, LL_PRETTY_FUNCTION, ##__VA_ARGS__) + /***************************************************************************** * lua_push() wrappers for generic code *****************************************************************************/ @@ -297,6 +328,7 @@ auto lua_to(lua_State* L, int index) template auto lua_getfieldv(lua_State* L, int index, const char* k) { + lua_checkdelta(L); lluau_checkstack(L, 1); lua_getfield(L, index, k); LuaPopper pop(L, 1); @@ -307,6 +339,7 @@ auto lua_getfieldv(lua_State* L, int index, const char* k) template auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value) { + lua_checkdelta(L); lluau_checkstack(L, 1); lua_push(L, value); lua_setfield(L, index, k); @@ -316,6 +349,7 @@ auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value) template auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) { + lua_checkdelta(L); lluau_checkstack(L, 1); lua_pushlstring(L, k.data(), k.length()); lua_rawget(L, index); @@ -327,6 +361,7 @@ auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) template void lua_rawsetfield(lua_State* L, int index, const std::string_view& k, const T& value) { + lua_checkdelta(L); lluau_checkstack(L, 2); lua_pushlstring(L, k.data(), k.length()); lua_push(L, value); @@ -433,6 +468,7 @@ DistinctInt TypeTag::value; template void lua_emplace(lua_State* L, ARGS&&... args) { + lua_checkdelta(L, 1); lluau_checkstack(L, 1); int tag{ TypeTag::value }; if (! lua_getuserdatadtor(L, tag)) @@ -467,6 +503,7 @@ void lua_emplace(lua_State* L, ARGS&&... args) template T* lua_toclass(lua_State* L, int index) { + lua_checkdelta(L); // get void* pointer to userdata (if that's what it is) void* ptr{ lua_touserdatatagged(L, index, TypeTag::value) }; // Derive the T* from ptr. If in future lua_emplace() must manually diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 0b1a73f4e9..6de8829308 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -53,6 +53,7 @@ std::map LLLUAmanager::sScriptNames; lua_function(sleep, "sleep(seconds): pause the running coroutine") { + lua_checkdelta(L, -1); F32 seconds = lua_tonumber(L, -1); lua_pop(L, 1); llcoro::suspendUntilTimeout(seconds); @@ -125,6 +126,7 @@ lua_function(print_warning, "print_warning(args...): WARNING level logging") lua_function(post_on, "post_on(pumpname, data): post specified data to specified LLEventPump") { + lua_checkdelta(L, -2); std::string pumpname{ lua_tostdstring(L, 1) }; LLSD data{ lua_tollsd(L, 2) }; lua_pop(L, 2); @@ -139,6 +141,7 @@ lua_function(get_event_pumps, "Events posted to replypump are queued for get_event_next().\n" "post_on(commandpump, ...) to engage LLEventAPI operations (see helpleap()).") { + lua_checkdelta(L, 2); lluau_checkstack(L, 2); auto& listener{ LuaState::obtainListener(L) }; // return the reply pump name and the command pump name on caller's lua_State @@ -153,6 +156,7 @@ lua_function(get_event_next, "is returned by get_event_pumps(). Blocks the calling chunk until an\n" "event becomes available.") { + lua_checkdelta(L, 2); lluau_checkstack(L, 2); auto& listener{ LuaState::obtainListener(L) }; const auto& [pump, data]{ listener.getNext() }; @@ -271,6 +275,7 @@ std::string read_file(const std::string &name) lua_function(require, "require(module_name) : load module_name.lua from known places") { + lua_checkdelta(L); std::string name = lua_tostdstring(L, 1); lua_pop(L, 1); -- cgit v1.2.3 From d37aa5c1823fcb202e87b1457842e49655c72b95 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 20 Aug 2024 21:22:46 -0400 Subject: Defend timers.Timer(iterate=True) against long callbacks. Specifically, defend against a callback that runs so long it suspends at a point after the next timer tick. --- indra/newview/scripts/lua/require/timers.lua | 34 +++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/require/timers.lua b/indra/newview/scripts/lua/require/timers.lua index e4938078dc..ab1615ffbf 100644 --- a/indra/newview/scripts/lua/require/timers.lua +++ b/indra/newview/scripts/lua/require/timers.lua @@ -34,35 +34,53 @@ function timers.Timer:new(delay, callback, iterate) callback = callback or function() obj:tick() end - local first = true + local calls = 0 if iterate then + -- With iterative timers, beware of running a timer callback which + -- performs async actions lasting longer than the timer interval. The + -- lengthy callback suspends, allowing leap to retrieve the next + -- event, which is a timer tick. leap calls a new instance of the + -- callback, even though the previous callback call is still + -- suspended... etc. 'in_callback' defends against that recursive + -- case. Rather than re-enter the suspended callback, drop the + -- too-soon timer event. (We could count the too-soon timer events and + -- iterate calling the callback, but it's a bathtub problem: the + -- callback could end up getting farther and farther behind.) + local in_callback = false obj.id = leap.eventstream( 'Timers', {op='scheduleEvery', every=delay}, function (event) local reqid = event.reqid - if first then - first = false + calls += 1 + if calls == 1 then dbg('timer(%s) first callback', reqid) -- discard the first (immediate) response: don't call callback return nil else - dbg('timer(%s) nth callback', reqid) - return callback(event) + if in_callback then + dbg('dropping timer(%s) callback %d', reqid, calls) + else + dbg('timer(%s) callback %d', reqid, calls) + in_callback = true + local ret = callback(event) + in_callback = false + return ret + end end end ).reqid - else + else -- (not iterate) obj.id = leap.eventstream( 'Timers', {op='scheduleAfter', after=delay}, function (event) + calls += 1 -- Arrange to return nil the first time, true the second. This -- callback is called immediately with the response to -- 'scheduleAfter', and if we immediately returned true, we'd -- be done, and the subsequent timer event would be discarded. - if first then - first = false + if calls == 1 then -- Caller doesn't expect an immediate callback. return nil else -- cgit v1.2.3 From 409745eb370d7609df70da81584142dffbfe9d3f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 20 Aug 2024 21:41:26 -0400 Subject: Fix a couple more set_interrupts_counter() calls. --- indra/newview/llluamanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 6de8829308..22b51d7b72 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -57,7 +57,7 @@ lua_function(sleep, "sleep(seconds): pause the running coroutine") F32 seconds = lua_tonumber(L, -1); lua_pop(L, 1); llcoro::suspendUntilTimeout(seconds); - lluau::set_interrupts_counter(L, 0); + LuaState::getParent(L).set_interrupts_counter(0); return 0; }; @@ -162,7 +162,7 @@ lua_function(get_event_next, const auto& [pump, data]{ listener.getNext() }; lua_pushstdstring(L, pump); lua_pushllsd(L, data); - lluau::set_interrupts_counter(L, 0); + LuaState::getParent(L).set_interrupts_counter(0); return 2; } -- cgit v1.2.3 From f4b650b100c120ed99208545864b0a7f36ce058d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 21 Aug 2024 09:12:52 -0400 Subject: Suppress ~LuaStackDelta() verification during stack unwinding. Otherwise, an exception raised in the block containing a LuaStackDelta instance -- that might be caught -- would result in an LL_ERRS() crash. We can't expect a block exited via exception to keep its contract wrt the Lua data stack. --- indra/llcommon/lua_function.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index ad77a1e040..1e9bbdd651 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -1056,7 +1056,10 @@ LuaStackDelta::LuaStackDelta(lua_State* L, const std::string& where, int delta): LuaStackDelta::~LuaStackDelta() { auto depth{ lua_gettop(L) }; - if (mDepth + mDelta != depth) + // If we're unwinding the stack due to an exception, then of course we + // can't expect the logic in the block containing this LuaStackDelta + // instance to keep its contract wrt the Lua data stack. + if (std::uncaught_exceptions() == 0 && mDepth + mDelta != depth) { LL_ERRS("Lua") << mWhere << ": Lua stack went from " << mDepth << " to " << depth; if (mDelta) -- cgit v1.2.3 From f2abd050bce3c4132f12d962fc6436d1f06666bd Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 21 Aug 2024 09:29:26 -0400 Subject: Improve diagnostic output for Lua atexit() functions. --- indra/llcommon/lua_function.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 1e9bbdd651..452bc24b16 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -496,6 +496,14 @@ LuaState::~LuaState() // stack contains Registry.atexit if (lua_istable(mState, -1)) { + // We happen to know that Registry.atexit is built by appending array + // entries using table.insert(). That's important because it means + // there are no holes, and therefore lua_objlen() should be correct. + // That's important because we walk the atexit table backwards, to + // destroy last the things we created (passed to LL.atexit()) first. + int len(lua_objlen(mState, -1)); + LL_DEBUGS("Lua") << "Registry.atexit is a table with " << len << " entries" << LL_ENDL; + // Push debug.traceback() onto the stack as lua_pcall()'s error // handler function. On error, lua_pcall() calls the specified error // handler function with the original error message; the message @@ -509,12 +517,7 @@ LuaState::~LuaState() lua_remove(mState, -2); // stack now contains atexit, debug.traceback() - // We happen to know that Registry.atexit is built by appending array - // entries using table.insert(). That's important because it means - // there are no holes, and therefore lua_objlen() should be correct. - // That's important because we walk the atexit table backwards, to - // destroy last the things we created (passed to LL.atexit()) first. - for (int i(lua_objlen(mState, -2)); i >= 1; --i) + for (int i(len); i >= 1; --i) { lua_pushinteger(mState, i); // stack contains Registry.atexit, debug.traceback(), i @@ -524,13 +527,15 @@ LuaState::~LuaState() // Use lua_pcall() because errors in any one atexit() function // shouldn't cancel the rest of them. Pass debug.traceback() as // the error handler function. + LL_DEBUGS("Lua") << "Calling atexit(" << i << ")" << LL_ENDL; if (lua_pcall(mState, 0, 0, -2) != LUA_OK) { auto error{ lua_tostdstring(mState, -1) }; - LL_WARNS("Lua") << "atexit() function error: " << error << LL_ENDL; + LL_WARNS("Lua") << "atexit(" << i << ") error: " << error << LL_ENDL; // pop error message lua_pop(mState, 1); } + LL_DEBUGS("Lua") << "atexit(" << i << ") done" << LL_ENDL; // lua_pcall() has already popped atexit[i]: // stack contains atexit, debug.traceback() } -- cgit v1.2.3 From 86eec90d97c03ac0f6be8897041185c6b5601968 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Wed, 21 Aug 2024 20:43:38 +0300 Subject: Add item limit for collectDescendentsIf func; add demo script --- indra/newview/llinventoryfunctions.h | 2 ++ indra/newview/llinventorylistener.cpp | 26 +++++++++++++++++++---- indra/newview/llinventorylistener.h | 4 ++++ indra/newview/llinventorymodel.cpp | 8 +++++++ indra/newview/scripts/lua/require/LLInventory.lua | 3 ++- indra/newview/scripts/lua/test_LLInventory.lua | 21 ++++++++++++++++++ 6 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 indra/newview/scripts/lua/test_LLInventory.lua (limited to 'indra') diff --git a/indra/newview/llinventoryfunctions.h b/indra/newview/llinventoryfunctions.h index 78077d2007..f6d910820d 100644 --- a/indra/newview/llinventoryfunctions.h +++ b/indra/newview/llinventoryfunctions.h @@ -189,6 +189,8 @@ public: virtual ~LLInventoryCollectFunctor(){}; virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) = 0; + virtual bool exceedsLimit() { return false; } + static bool itemTransferCommonlyAllowed(const LLInventoryItem* item); }; diff --git a/indra/newview/llinventorylistener.cpp b/indra/newview/llinventorylistener.cpp index 4beb59c58e..4ff25f7f5c 100644 --- a/indra/newview/llinventorylistener.cpp +++ b/indra/newview/llinventorylistener.cpp @@ -33,6 +33,8 @@ #include "llwearableitemslist.h" #include "stringize.h" +static const F32 MAX_ITEM_LIMIT = 100; + LLInventoryListener::LLInventoryListener() : LLEventAPI("LLInventory", "API for interactions with viewer Inventory items") @@ -68,7 +70,8 @@ LLInventoryListener::LLInventoryListener() "Return the descendents(both items and folders) of the [\"folder_id\"], if it passes specified filters:\n" "[\"name\"] is a substring of object's name,\n" "[\"desc\"] is a substring of object's description,\n" - "asset [\"type\"] corresponds to the object's asset type\n" + "asset [\"type\"] corresponds to the object's asset type\n" + "[\"item_limit\"] sets item count limit in reply, maximum and default is 100\n" "[\"filter_links\"]: EXCLUDE_LINKS - don't show links, ONLY_LINKS - only show links, INCLUDE_LINKS - show links too (default)", &LLInventoryListener::collectDescendentsIf, llsd::map("folder_id", LLSD(), "reply", LLSD())); @@ -171,9 +174,11 @@ void LLInventoryListener::collectDescendentsIf(LLSD const &data) } } -LLFilteredCollector::LLFilteredCollector(LLSD const &data) - : mType(LLAssetType::EType::AT_UNKNOWN), - mLinkFilter(INCLUDE_LINKS) +LLFilteredCollector::LLFilteredCollector(LLSD const &data) : + mType(LLAssetType::EType::AT_UNKNOWN), + mLinkFilter(INCLUDE_LINKS), + mItemLimit(MAX_ITEM_LIMIT), + mItemCount(0) { if (data.has("name")) { @@ -198,6 +203,10 @@ LLFilteredCollector::LLFilteredCollector(LLSD const &data) mLinkFilter = ONLY_LINKS; } } + if (data.has("item_limit")) + { + mItemLimit = llclamp(data["item_limit"].asInteger(), 1, MAX_ITEM_LIMIT); + } } bool LLFilteredCollector::operator()(LLInventoryCategory *cat, LLInventoryItem *item) @@ -206,9 +215,18 @@ bool LLFilteredCollector::operator()(LLInventoryCategory *cat, LLInventoryItem * passed = passed && checkagainstNameDesc(cat, item); passed = passed && checkagainstLinks(cat, item); + if (passed) + { + ++mItemCount; + } return passed; } +bool LLFilteredCollector::exceedsLimit() +{ + return (mItemLimit <= mItemCount); +} + bool LLFilteredCollector::checkagainstNameDesc(LLInventoryCategory *cat, LLInventoryItem *item) { std::string name, desc; diff --git a/indra/newview/llinventorylistener.h b/indra/newview/llinventorylistener.h index 83e1751e1b..2c1eb2cb6d 100644 --- a/indra/newview/llinventorylistener.h +++ b/indra/newview/llinventorylistener.h @@ -56,6 +56,7 @@ struct LLFilteredCollector : public LLInventoryCollectFunctor LLFilteredCollector(LLSD const &data); virtual ~LLFilteredCollector() {} virtual bool operator()(LLInventoryCategory *cat, LLInventoryItem *item); + virtual bool exceedsLimit(); protected: bool checkagainstType(LLInventoryCategory *cat, LLInventoryItem *item); @@ -66,6 +67,9 @@ struct LLFilteredCollector : public LLInventoryCollectFunctor std::string mName; std::string mDesc; EFilterLink mLinkFilter; + + S32 mItemLimit; + S32 mItemCount; }; #endif // LL_LLINVENTORYLISTENER_H diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index 5325c28abf..c98c3482d0 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -1289,6 +1289,10 @@ void LLInventoryModel::collectDescendentsIf(const LLUUID& id, S32 count = cat_array->size(); for(S32 i = 0; i < count; ++i) { + if (add.exceedsLimit()) + { + break; + } LLViewerInventoryCategory* cat = cat_array->at(i); if(add(cat,NULL)) { @@ -1307,6 +1311,10 @@ void LLInventoryModel::collectDescendentsIf(const LLUUID& id, S32 count = item_array->size(); for(S32 i = 0; i < count; ++i) { + if (add.exceedsLimit()) + { + break; + } item = item_array->at(i); if(add(NULL, item)) { diff --git a/indra/newview/scripts/lua/require/LLInventory.lua b/indra/newview/scripts/lua/require/LLInventory.lua index e6a347532b..b3f817da94 100644 --- a/indra/newview/scripts/lua/require/LLInventory.lua +++ b/indra/newview/scripts/lua/require/LLInventory.lua @@ -36,9 +36,10 @@ end -- [, name] -- name (substring) -- [, desc] -- description (substring) -- [, type] -- asset type +-- [, item_limit] -- item count limit in reply, maximum and default is 100 -- [, filter_links]} -- EXCLUDE_LINKS - don't show links, ONLY_LINKS - only show links, INCLUDE_LINKS - show links too (default) function LLInventory.collectDescendentsIf(...) - local args = mapargs('folder_id,name,desc,type,filter_links', ...) + local args = mapargs('folder_id,name,desc,type,filter_links,item_limit', ...) args.op = 'collectDescendentsIf' return leap.request('LLInventory', args) end diff --git a/indra/newview/scripts/lua/test_LLInventory.lua b/indra/newview/scripts/lua/test_LLInventory.lua new file mode 100644 index 0000000000..dc6eb243ad --- /dev/null +++ b/indra/newview/scripts/lua/test_LLInventory.lua @@ -0,0 +1,21 @@ +inspect = require 'inspect' +LLInventory = require 'LLInventory' + +-- Get 'My Landmarks' folder id (you can see all folder types via LLInventory.getFolderTypeNames()) +my_landmarks_id = LLInventory.getBasicFolderID('landmark') +-- Get 3 landmarks from the 'My Landmarks' folder (you can see all folder types via LLInventory.getAssetTypeNames()) +landmarks = LLInventory.collectDescendentsIf{folder_id=my_landmarks_id, type="landmark", item_limit=3} +print(inspect(landmarks)) + +-- Get 'Calling Cards' folder id +calling_cards_id = LLInventory.getBasicFolderID('callcard') +-- Get all items located directly in 'Calling Cards' folder +calling_cards = LLInventory.getDirectDescendents(calling_cards_id).items + +-- Print a random calling card name from 'Calling Cards' folder +local card_names = {} +for _, value in pairs(calling_cards) do + table.insert(card_names, value.name) +end +math.randomseed(os.time()) +print("Random calling card: " .. inspect(card_names[math.random(#card_names)])) -- cgit v1.2.3 From 8887711019f3b37bc830a51609a4940fe10d7cec Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Wed, 21 Aug 2024 23:12:21 +0300 Subject: mac build fix --- indra/newview/llinventorylistener.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/newview/llinventorylistener.cpp b/indra/newview/llinventorylistener.cpp index 4ff25f7f5c..0aa1208d83 100644 --- a/indra/newview/llinventorylistener.cpp +++ b/indra/newview/llinventorylistener.cpp @@ -182,11 +182,11 @@ LLFilteredCollector::LLFilteredCollector(LLSD const &data) : { if (data.has("name")) { - mName = data["name"]; + mName = data["name"].asString(); } if (data.has("desc")) { - mDesc = data["desc"]; + mDesc = data["desc"].asString(); } if (data.has("type")) { -- cgit v1.2.3 From 073d3d0e04a2ebe35a201eae9c6dad4b7ae5dc88 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Thu, 22 Aug 2024 23:29:28 +0300 Subject: Fix for #2385: say, shout and whisper messages from the script should be displayed consistently --- indra/newview/llviewermessage.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'indra') diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 26b088e390..4922e3c9f9 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -2646,19 +2646,21 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) else { chat.mText = ""; + std::string chat_text(mesg); + auto [msg_without_prefix, is_lua] = LLStringUtil::withoutPrefix(mesg, LUA_PREFIX); switch(chat.mChatType) { case CHAT_TYPE_WHISPER: - chat.mText = LLTrans::getString("whisper") + " "; + case CHAT_TYPE_SHOUT: + chat_text = LLTrans::getString(chat.mChatType == CHAT_TYPE_WHISPER ? "whisper" : "shout") + " " + msg_without_prefix; + if(is_lua) + chat_text = LUA_PREFIX + chat_text; break; case CHAT_TYPE_DEBUG_MSG: case CHAT_TYPE_OWNER: case CHAT_TYPE_NORMAL: case CHAT_TYPE_DIRECT: break; - case CHAT_TYPE_SHOUT: - chat.mText = LLTrans::getString("shout") + " "; - break; case CHAT_TYPE_START: case CHAT_TYPE_STOP: LL_WARNS("Messaging") << "Got chat type start/stop in main chat processing." << LL_ENDL; @@ -2668,7 +2670,7 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) break; } - chat.mText += mesg; + chat.mText += chat_text; } // We have a real utterance now, so can stop showing "..." and proceed. -- cgit v1.2.3 From a80b9487dc7c893f5e96f48f15140a5f82b99e30 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 23 Aug 2024 17:18:14 -0400 Subject: Allow UI to have lazily-loaded submodules. Equip UI with an __index metamethod. When someone references an unknown key/field in UI, require() that module and cache it for future reference. Add util.setmetamethods() as a way to find or create a metatable on a specified table containing specified metamethods. Exercise the new functionality by referencing UI.popup in test_popup.lua. --- indra/newview/scripts/lua/require/UI.lua | 22 ++++++++++++-- indra/newview/scripts/lua/require/util.lua | 46 +++++++++++++++++++++++------- indra/newview/scripts/lua/test_popup.lua | 3 +- 3 files changed, 57 insertions(+), 14 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua index 464e6547ea..969a2cbded 100644 --- a/indra/newview/scripts/lua/require/UI.lua +++ b/indra/newview/scripts/lua/require/UI.lua @@ -1,10 +1,26 @@ -- Engage the viewer's UI local leap = require 'leap' -local Timer = (require 'timers').Timer local mapargs = require 'mapargs' - -local UI = {} +local Timer = (require 'timers').Timer +local util = require 'util' + +-- Allow lazily accessing certain other modules on demand, e.g. a reference to +-- UI.Floater lazily loads the Floater module. Use of UI's __index metamethod +-- theoretically permits any other module you can require() to appear as a +-- submodule of UI, but it doesn't make sense to support (e.g.) UI.Queue. +local submods = { 'Floater', 'popup' } +local UI = util.setmetamethods{ + __index=function(t, key) + if not table.find(submods, key) then + error(`Invalid UI submodule {key}`, 2) + end + local mod = require(key) + -- cache the submodule + t[key] = mod + return mod + end +} -- *************************************************************************** -- registered menu actions diff --git a/indra/newview/scripts/lua/require/util.lua b/indra/newview/scripts/lua/require/util.lua index bfbfc8637c..a000359226 100644 --- a/indra/newview/scripts/lua/require/util.lua +++ b/indra/newview/scripts/lua/require/util.lua @@ -15,16 +15,9 @@ local util = {} -- util.classctor(MyClass, MyClass.construct) -- return MyClass function util.classctor(class, ctor) - -- get the metatable for the passed class - local mt = getmetatable(class) - if mt == nil then - -- if it doesn't already have a metatable, then create one - mt = {} - setmetatable(class, mt) - end - -- now that class has a metatable, set its __call method to the specified - -- constructor method (class.new if not specified) - mt.__call = ctor or class.new + -- set class's __call metamethod to the specified constructor function + -- (class.new if not specified) + util.setmetamethods{class, __call=(ctor or class.new)} end -- check if array-like table contains certain value @@ -66,4 +59,37 @@ function util.equal(t1, t2) return util.empty(temp) end +-- Find or create the metatable for a specified table (a new empty table if +-- omitted), and to that metatable assign the specified keys. +-- Setting multiple keys at once is more efficient than a function to set only +-- one at a time, e.g. setametamethod(). +-- t = util.setmetamethods{__index=readfunc, __len=lenfunc} +-- returns a new table with specified metamethods __index, __len +-- util.setmetamethods{t, __call=action} +-- finds or creates the metatable for existing table t and sets __call +-- util.setmetamethods{table=t, __call=action} +-- same as util.setmetamethods{t, __call=action} +function util.setmetamethods(specs) + -- first determine the target table + assert(not (specs.table and specs[1]), + "Pass setmetamethods table either as positional or table=, not both") + local t = specs.table or specs[1] or {} + -- remove both ways of specifying table, leaving only the metamethods + specs.table = nil + specs[1] = nil + local mt = getmetatable(t) + if not mt then + -- t doesn't already have a metatable: just set specs + setmetatable(t, specs) + else + -- t already has a metatable: copy specs into it + local key, value + for key, value in pairs(specs) do + mt[key] = value + end + end + -- having set or enriched t's metatable, return t + return t +end + return util diff --git a/indra/newview/scripts/lua/test_popup.lua b/indra/newview/scripts/lua/test_popup.lua index e48f89c3a7..156c09c78f 100644 --- a/indra/newview/scripts/lua/test_popup.lua +++ b/indra/newview/scripts/lua/test_popup.lua @@ -1,4 +1,5 @@ -popup = require 'popup' +UI = require 'UI' +popup = UI.popup response = popup:alert('This just has a Close button') response = popup:alertOK(string.format('You said "%s", is that OK?', next(response))) -- cgit v1.2.3 From e4a710296943674573be800f5233b24214440929 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 23 Aug 2024 20:55:22 -0400 Subject: Look for lazy UI submodules in a require/UI subdirectory. This way encourages "UI = require 'UI'; UI.Floater" instead of just "Floater = require 'Floater'". Moreover, now we don't need UI to maintain a list of allowed submodules; that's effected by membership in the subdirectory. --- indra/newview/scripts/lua/require/Floater.lua | 146 ----------------------- indra/newview/scripts/lua/require/UI.lua | 12 +- indra/newview/scripts/lua/require/UI/Floater.lua | 146 +++++++++++++++++++++++ indra/newview/scripts/lua/require/UI/popup.lua | 53 ++++++++ indra/newview/scripts/lua/require/popup.lua | 53 -------- 5 files changed, 202 insertions(+), 208 deletions(-) delete mode 100644 indra/newview/scripts/lua/require/Floater.lua create mode 100644 indra/newview/scripts/lua/require/UI/Floater.lua create mode 100644 indra/newview/scripts/lua/require/UI/popup.lua delete mode 100644 indra/newview/scripts/lua/require/popup.lua (limited to 'indra') diff --git a/indra/newview/scripts/lua/require/Floater.lua b/indra/newview/scripts/lua/require/Floater.lua deleted file mode 100644 index d057a74386..0000000000 --- a/indra/newview/scripts/lua/require/Floater.lua +++ /dev/null @@ -1,146 +0,0 @@ --- Floater base class - -local leap = require 'leap' -local fiber = require 'fiber' -local util = require 'util' - --- list of all the events that a LLLuaFloater might send -local event_list = leap.request("LLFloaterReg", {op="getFloaterEvents"}).events -local event_set = {} -for _, event in pairs(event_list) do - event_set[event] = true -end - -local function _event(event_name) - if not event_set[event_name] then - error("Incorrect event name: " .. event_name, 3) - end - return event_name -end - --- --------------------------------------------------------------------------- -local Floater = {} - --- Pass: --- relative file path to floater's XUI definition file --- optional: sign up for additional events for defined control --- {={action1, action2, ...}} -function Floater:new(path, extra) - local obj = setmetatable({}, self) - self.__index = self - - local path_parts = string.split(path, '/') - obj.name = 'Floater ' .. path_parts[#path_parts] - - obj._command = {op="showLuaFloater", xml_path=LL.abspath(path)} - if extra then - -- validate each of the actions for each specified control - for control, actions in pairs(extra) do - for _, action in pairs(actions) do - _event(action) - end - end - obj._command.extra_events = extra - end - - return obj -end - -util.classctor(Floater) - -function Floater:show() - -- leap.eventstream() returns the first response, and launches a - -- background fiber to call the passed callback with all subsequent - -- responses. - local event = leap.eventstream( - 'LLFloaterReg', - self._command, - -- handleEvents() returns false when done. - -- eventstream() expects a true return when done. - function(event) return not self:handleEvents(event) end) - self._pump = event.command_name - -- we might need the returned reqid to cancel the eventstream() fiber - self.reqid = event.reqid - - -- The response to 'showLuaFloater' *is* the 'post_build' event. Check if - -- subclass has a post_build() method. Honor the convention that if - -- handleEvents() returns false, we're done. - if not self:handleEvents(event) then - return - end -end - -function Floater:post(action) - leap.send(self._pump, action) -end - -function Floater:request(action) - return leap.request(self._pump, action) -end - --- local inspect = require 'inspect' - -function Floater:handleEvents(event_data) - local event = event_data.event - if event_set[event] == nil then - LL.print_warning(string.format('%s received unknown event %q', self.name, event)) - end - - -- Before checking for a general (e.g.) commit() method, first look for - -- commit_ctrl_name(): in other words, concatenate the event name with the - -- ctrl_name, with an underscore between. If there exists such a specific - -- method, call that. - local handler, ret - if event_data.ctrl_name then - local specific = event .. '_' .. event_data.ctrl_name - handler = self[specific] - if handler then - ret = handler(self, event_data) - -- Avoid 'return ret or true' because we explicitly want to allow - -- the handler to return false. - if ret ~= nil then - return ret - else - return true - end - end - end - - -- No specific "event_on_ctrl()" method found; try just "event()" - handler = self[event] - if handler then - ret = handler(self, event_data) - if ret ~= nil then - return ret - end --- else --- print(string.format('%s ignoring event %s', self.name, inspect(event_data))) - end - - -- We check for event() method before recognizing floater_close in case - -- the consumer needs to react specially to closing the floater. Now that - -- we've checked, recognize it ourselves. Returning false terminates the - -- anonymous fiber function launched by leap.eventstream(). - if event == _event('floater_close') then - LL.print_warning(self.name .. ' closed') - return false - end - return true -end - --- onCtrl() permits a different dispatch style in which the general event() --- method explicitly calls (e.g.) --- self:onCtrl(event_data, { --- ctrl_name=function() --- self:post(...) --- end, --- ... --- }) -function Floater:onCtrl(event_data, ctrl_map) - local handler = ctrl_map[event_data.ctrl_name] - if handler then - handler() - end -end - -return Floater diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua index 969a2cbded..2df70fd453 100644 --- a/indra/newview/scripts/lua/require/UI.lua +++ b/indra/newview/scripts/lua/require/UI.lua @@ -5,17 +5,11 @@ local mapargs = require 'mapargs' local Timer = (require 'timers').Timer local util = require 'util' --- Allow lazily accessing certain other modules on demand, e.g. a reference to --- UI.Floater lazily loads the Floater module. Use of UI's __index metamethod --- theoretically permits any other module you can require() to appear as a --- submodule of UI, but it doesn't make sense to support (e.g.) UI.Queue. -local submods = { 'Floater', 'popup' } +-- Allow lazily accessing UI submodules on demand, e.g. a reference to +-- UI.Floater lazily loads the UI/Floater module. local UI = util.setmetamethods{ __index=function(t, key) - if not table.find(submods, key) then - error(`Invalid UI submodule {key}`, 2) - end - local mod = require(key) + local mod = require('UI/' .. key) -- cache the submodule t[key] = mod return mod diff --git a/indra/newview/scripts/lua/require/UI/Floater.lua b/indra/newview/scripts/lua/require/UI/Floater.lua new file mode 100644 index 0000000000..d057a74386 --- /dev/null +++ b/indra/newview/scripts/lua/require/UI/Floater.lua @@ -0,0 +1,146 @@ +-- Floater base class + +local leap = require 'leap' +local fiber = require 'fiber' +local util = require 'util' + +-- list of all the events that a LLLuaFloater might send +local event_list = leap.request("LLFloaterReg", {op="getFloaterEvents"}).events +local event_set = {} +for _, event in pairs(event_list) do + event_set[event] = true +end + +local function _event(event_name) + if not event_set[event_name] then + error("Incorrect event name: " .. event_name, 3) + end + return event_name +end + +-- --------------------------------------------------------------------------- +local Floater = {} + +-- Pass: +-- relative file path to floater's XUI definition file +-- optional: sign up for additional events for defined control +-- {={action1, action2, ...}} +function Floater:new(path, extra) + local obj = setmetatable({}, self) + self.__index = self + + local path_parts = string.split(path, '/') + obj.name = 'Floater ' .. path_parts[#path_parts] + + obj._command = {op="showLuaFloater", xml_path=LL.abspath(path)} + if extra then + -- validate each of the actions for each specified control + for control, actions in pairs(extra) do + for _, action in pairs(actions) do + _event(action) + end + end + obj._command.extra_events = extra + end + + return obj +end + +util.classctor(Floater) + +function Floater:show() + -- leap.eventstream() returns the first response, and launches a + -- background fiber to call the passed callback with all subsequent + -- responses. + local event = leap.eventstream( + 'LLFloaterReg', + self._command, + -- handleEvents() returns false when done. + -- eventstream() expects a true return when done. + function(event) return not self:handleEvents(event) end) + self._pump = event.command_name + -- we might need the returned reqid to cancel the eventstream() fiber + self.reqid = event.reqid + + -- The response to 'showLuaFloater' *is* the 'post_build' event. Check if + -- subclass has a post_build() method. Honor the convention that if + -- handleEvents() returns false, we're done. + if not self:handleEvents(event) then + return + end +end + +function Floater:post(action) + leap.send(self._pump, action) +end + +function Floater:request(action) + return leap.request(self._pump, action) +end + +-- local inspect = require 'inspect' + +function Floater:handleEvents(event_data) + local event = event_data.event + if event_set[event] == nil then + LL.print_warning(string.format('%s received unknown event %q', self.name, event)) + end + + -- Before checking for a general (e.g.) commit() method, first look for + -- commit_ctrl_name(): in other words, concatenate the event name with the + -- ctrl_name, with an underscore between. If there exists such a specific + -- method, call that. + local handler, ret + if event_data.ctrl_name then + local specific = event .. '_' .. event_data.ctrl_name + handler = self[specific] + if handler then + ret = handler(self, event_data) + -- Avoid 'return ret or true' because we explicitly want to allow + -- the handler to return false. + if ret ~= nil then + return ret + else + return true + end + end + end + + -- No specific "event_on_ctrl()" method found; try just "event()" + handler = self[event] + if handler then + ret = handler(self, event_data) + if ret ~= nil then + return ret + end +-- else +-- print(string.format('%s ignoring event %s', self.name, inspect(event_data))) + end + + -- We check for event() method before recognizing floater_close in case + -- the consumer needs to react specially to closing the floater. Now that + -- we've checked, recognize it ourselves. Returning false terminates the + -- anonymous fiber function launched by leap.eventstream(). + if event == _event('floater_close') then + LL.print_warning(self.name .. ' closed') + return false + end + return true +end + +-- onCtrl() permits a different dispatch style in which the general event() +-- method explicitly calls (e.g.) +-- self:onCtrl(event_data, { +-- ctrl_name=function() +-- self:post(...) +-- end, +-- ... +-- }) +function Floater:onCtrl(event_data, ctrl_map) + local handler = ctrl_map[event_data.ctrl_name] + if handler then + handler() + end +end + +return Floater diff --git a/indra/newview/scripts/lua/require/UI/popup.lua b/indra/newview/scripts/lua/require/UI/popup.lua new file mode 100644 index 0000000000..3aaadf85ba --- /dev/null +++ b/indra/newview/scripts/lua/require/UI/popup.lua @@ -0,0 +1,53 @@ +local leap = require 'leap' +local mapargs = require 'mapargs' + +-- notification is any name defined in notifications.xml as +-- +-- vars is a table providing values for [VAR] substitution keys in the +-- notification body +-- payload prepopulates the response table +-- wait=false means fire and forget, otherwise wait for user response +local popup_meta = { + -- setting this function as getmetatable(popup).__call() means this gets + -- called when a consumer calls popup(notification, vars, payload) + __call = function(self, ...) + local args = mapargs('notification,vars,payload,wait', ...) + -- we use convenience argument names different from 'LLNotifications' + -- listener + args.name = args.notification + args.notification = nil + args.substitutions = args.vars + args.vars = nil + local wait = args.wait + args.wait = nil + args.op = 'requestAdd' + -- Specifically test (wait == false), NOT (not wait), because we treat + -- nil (omitted, default true) differently than false (explicitly + -- DON'T wait). + if wait == false then + leap.send('LLNotifications', args) + else + return leap.request('LLNotifications', args).response + end + end +} + +local popup = setmetatable({}, popup_meta) + +function popup:alert(message) + return self('GenericAlert', {MESSAGE=message}) +end + +function popup:alertOK(message) + return self('GenericAlertOK', {MESSAGE=message}) +end + +function popup:alertYesCancel(message) + return self('GenericAlertYesCancel', {MESSAGE=message}) +end + +function popup:tip(message) + self{'SystemMessageTip', {MESSAGE=message}, wait=false} +end + +return popup diff --git a/indra/newview/scripts/lua/require/popup.lua b/indra/newview/scripts/lua/require/popup.lua deleted file mode 100644 index 3aaadf85ba..0000000000 --- a/indra/newview/scripts/lua/require/popup.lua +++ /dev/null @@ -1,53 +0,0 @@ -local leap = require 'leap' -local mapargs = require 'mapargs' - --- notification is any name defined in notifications.xml as --- --- vars is a table providing values for [VAR] substitution keys in the --- notification body --- payload prepopulates the response table --- wait=false means fire and forget, otherwise wait for user response -local popup_meta = { - -- setting this function as getmetatable(popup).__call() means this gets - -- called when a consumer calls popup(notification, vars, payload) - __call = function(self, ...) - local args = mapargs('notification,vars,payload,wait', ...) - -- we use convenience argument names different from 'LLNotifications' - -- listener - args.name = args.notification - args.notification = nil - args.substitutions = args.vars - args.vars = nil - local wait = args.wait - args.wait = nil - args.op = 'requestAdd' - -- Specifically test (wait == false), NOT (not wait), because we treat - -- nil (omitted, default true) differently than false (explicitly - -- DON'T wait). - if wait == false then - leap.send('LLNotifications', args) - else - return leap.request('LLNotifications', args).response - end - end -} - -local popup = setmetatable({}, popup_meta) - -function popup:alert(message) - return self('GenericAlert', {MESSAGE=message}) -end - -function popup:alertOK(message) - return self('GenericAlertOK', {MESSAGE=message}) -end - -function popup:alertYesCancel(message) - return self('GenericAlertYesCancel', {MESSAGE=message}) -end - -function popup:tip(message) - self{'SystemMessageTip', {MESSAGE=message}, wait=false} -end - -return popup -- cgit v1.2.3 From 2e815acb529159f5b6f0a4365a2eaf64d35330cc Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 23 Aug 2024 21:28:03 -0400 Subject: Encapsulate the lazy submodule idiom as util.submoduledir(). --- indra/newview/scripts/lua/require/UI.lua | 9 +-------- indra/newview/scripts/lua/require/util.lua | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua index 2df70fd453..bbcae3514a 100644 --- a/indra/newview/scripts/lua/require/UI.lua +++ b/indra/newview/scripts/lua/require/UI.lua @@ -7,14 +7,7 @@ local util = require 'util' -- Allow lazily accessing UI submodules on demand, e.g. a reference to -- UI.Floater lazily loads the UI/Floater module. -local UI = util.setmetamethods{ - __index=function(t, key) - local mod = require('UI/' .. key) - -- cache the submodule - t[key] = mod - return mod - end -} +local UI = util.submoduledir({}, 'UI') -- *************************************************************************** -- registered menu actions diff --git a/indra/newview/scripts/lua/require/util.lua b/indra/newview/scripts/lua/require/util.lua index a000359226..40737a159a 100644 --- a/indra/newview/scripts/lua/require/util.lua +++ b/indra/newview/scripts/lua/require/util.lua @@ -92,4 +92,23 @@ function util.setmetamethods(specs) return t end +-- On the passed module (i.e. table), set an __index metamethod such that +-- referencing module.submodule lazily requires(path/submodule). +-- The loaded submodule is cached in the module table so it need not be passed +-- to require() again. +-- 'path', like any require() string, can be relative to LuaRequirePath. +-- Returns the enriched module, permitting e.g. +-- mymod = util.submoduledir({}, 'mymod') +function util.submoduledir(module, path) + return util.setmetamethods{ + module, + __index=function(t, key) + local mod = require(`{path}/{key}`) + -- cache the submodule + t[key] = mod + return mod + end + } +end + return util -- cgit v1.2.3 From e6d8379744b08f9a52af6734ba1f0e1b50fb5906 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 23 Aug 2024 22:39:30 -0400 Subject: Massage results from UI.popup() for ease of use. In particular, where the raw leap.request().response call would return {OK_okcancelbuttons=true}, just return the string 'OK' or 'Cancel'. Update existing consumer scripts. --- indra/newview/scripts/lua/require/UI/popup.lua | 77 +++++++++++++++------- indra/newview/scripts/lua/test_group_chat.lua | 7 +- .../scripts/lua/test_luafloater_speedometer.lua | 5 +- indra/newview/scripts/lua/test_popup.lua | 9 ++- indra/newview/scripts/lua/test_toolbars.lua | 6 +- 5 files changed, 67 insertions(+), 37 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/require/UI/popup.lua b/indra/newview/scripts/lua/require/UI/popup.lua index 3aaadf85ba..8ccf3b87f3 100644 --- a/indra/newview/scripts/lua/require/UI/popup.lua +++ b/indra/newview/scripts/lua/require/UI/popup.lua @@ -1,53 +1,82 @@ local leap = require 'leap' local mapargs = require 'mapargs' +local util = require 'util' -- notification is any name defined in notifications.xml as -- -- vars is a table providing values for [VAR] substitution keys in the -- notification body -- payload prepopulates the response table --- wait=false means fire and forget, otherwise wait for user response -local popup_meta = { - -- setting this function as getmetatable(popup).__call() means this gets - -- called when a consumer calls popup(notification, vars, payload) +-- wait=false means fire and forget, returning nil +-- wait=true waits for user response: +-- * If the viewer returns a table containing exactly one key=true pair, +-- popup() returns just that key. If the key is a string containing an +-- underscore, e.g. 'OK_okcancelbuttons', it's truncated at the first +-- underscore, e.g. 'OK'. +-- * Otherwise the viewer's response is returned unchanged. To suppress the +-- above transformations, pass a non-empty payload table; this will cause +-- the viewer to return a table with at least two keys. +local popup = util.setmetamethods{ + -- this gets called when a consumer calls popup(notification, vars, payload) __call = function(self, ...) local args = mapargs('notification,vars,payload,wait', ...) -- we use convenience argument names different from 'LLNotifications' -- listener - args.name = args.notification - args.notification = nil - args.substitutions = args.vars - args.vars = nil - local wait = args.wait - args.wait = nil - args.op = 'requestAdd' + newargs = {op='requestAdd', + name=args.notification, + substitutions=args.vars, + payload=args.payload} -- Specifically test (wait == false), NOT (not wait), because we treat -- nil (omitted, default true) differently than false (explicitly -- DON'T wait). - if wait == false then - leap.send('LLNotifications', args) + if args.wait == false then + leap.send('LLNotifications', newargs) else - return leap.request('LLNotifications', args).response + local response = leap.request('LLNotifications', newargs).response + -- response is typically a table. It might have multiple keys, + -- e.g. if caller passed non-empty payload. In that case, just + -- return the whole thing. + if type(response) ~= 'table' then + return response + end + -- get first key=value pair, if any + local key, value = next(response) + if (not key) or next(response, key) then + -- key == nil means response is empty + -- next(response, non-nil first key) ~= nil means at least two keys + return response + end + -- Here response is a table containing exactly one key. The + -- notifications system typically returns a table of the form + -- {OK_okcancelbuttons=true}, which is tricky to test for because it + -- varies with each set of buttons. + if value == true then + -- change {key=true} to plain key + response = key + if type(response) == 'string' then + -- change 'OK_okcancelbuttons' to plain 'OK' + response = string.split(response, '_')[1] + end + end + return response end end } -local popup = setmetatable({}, popup_meta) - -function popup:alert(message) - return self('GenericAlert', {MESSAGE=message}) +function popup:alert(message, payload) + return self('GenericAlert', {MESSAGE=message, payload=payload}) end -function popup:alertOK(message) - return self('GenericAlertOK', {MESSAGE=message}) +function popup:alertOK(message, payload) + return self('GenericAlertOK', {MESSAGE=message, payload=payload}) end -function popup:alertYesCancel(message) - return self('GenericAlertYesCancel', {MESSAGE=message}) +function popup:alertYesCancel(message, payload) + return self('GenericAlertYesCancel', {MESSAGE=message, payload=payload}) end -function popup:tip(message) - self{'SystemMessageTip', {MESSAGE=message}, wait=false} +function popup:tip(message, payload) + self{'SystemMessageTip', {MESSAGE=message, payload=payload}, wait=false} end return popup diff --git a/indra/newview/scripts/lua/test_group_chat.lua b/indra/newview/scripts/lua/test_group_chat.lua index eaff07ed14..373411c26c 100644 --- a/indra/newview/scripts/lua/test_group_chat.lua +++ b/indra/newview/scripts/lua/test_group_chat.lua @@ -1,15 +1,14 @@ LLChat = require 'LLChat' LLAgent = require 'LLAgent' -popup = require 'popup' +UI = require 'UI' -local OK = 'OK_okcancelbuttons' local GROUPS = LLAgent.getGroups() -- Choose one of the groups randomly and send group message math.randomseed(os.time()) group_info = GROUPS[math.random(#GROUPS)] LLChat.startGroupChat(group_info.id) -response = popup:alertYesCancel('Started group chat with ' .. group_info.name .. ' group. Send greetings?') -if next(response) == OK then +response = UI.popup:alertYesCancel('Started group chat with ' .. group_info.name .. ' group. Send greetings?') +if response == 'OK' then LLChat.sendGroupIM('Greetings', group_info.id) end diff --git a/indra/newview/scripts/lua/test_luafloater_speedometer.lua b/indra/newview/scripts/lua/test_luafloater_speedometer.lua index af7189a2cb..8bc18ad286 100644 --- a/indra/newview/scripts/lua/test_luafloater_speedometer.lua +++ b/indra/newview/scripts/lua/test_luafloater_speedometer.lua @@ -1,8 +1,9 @@ local Floater = require 'Floater' local leap = require 'leap' -local popup = require 'popup' local startup = require 'startup' local Timer = (require 'timers').Timer +local UI = require 'UI' +local popup = UI.popup local max_speed = 0 local flt = Floater("luafloater_speedometer.xml") startup.wait('STATE_STARTED') @@ -25,7 +26,7 @@ end msg = 'Are you sure you want to run this "speedometer" script?' response = popup:alertYesCancel(msg) -if response.OK_okcancelbuttons then +if response == 'OK' then flt:show() timer = Timer(1, idle, true) -- iterate end diff --git a/indra/newview/scripts/lua/test_popup.lua b/indra/newview/scripts/lua/test_popup.lua index 156c09c78f..7a11895669 100644 --- a/indra/newview/scripts/lua/test_popup.lua +++ b/indra/newview/scripts/lua/test_popup.lua @@ -1,7 +1,10 @@ UI = require 'UI' popup = UI.popup +startup = require 'startup' + +startup.wait('STATE_STARTED') response = popup:alert('This just has a Close button') -response = popup:alertOK(string.format('You said "%s", is that OK?', next(response))) -response = popup:alertYesCancel(string.format('You said "%s"', next(response))) -popup:tip(string.format('You said "%s"', next(response))) +response = popup:alertOK(string.format('You said "%s", is that OK?', response)) +response = popup:alertYesCancel(string.format('You said "%s"', response)) +popup:tip(string.format('You said "%s"', response)) diff --git a/indra/newview/scripts/lua/test_toolbars.lua b/indra/newview/scripts/lua/test_toolbars.lua index 9a832c5644..7683fca8a3 100644 --- a/indra/newview/scripts/lua/test_toolbars.lua +++ b/indra/newview/scripts/lua/test_toolbars.lua @@ -1,13 +1,11 @@ -popup = require 'popup' UI = require 'UI' -local OK = 'OK_okcancelbuttons' local BUTTONS = UI.getToolbarBtnNames() local TOOLBARS = {'left','right','bottom'} -- Clear the toolbars and then add the toolbar buttons to the random toolbar -response = popup:alertYesCancel('Toolbars will be randomly reshuffled. Proceed?') -if next(response) == OK then +response = UI.popup:alertYesCancel('Toolbars will be randomly reshuffled. Proceed?') +if response == 'OK' then UI.clearAllToolbars() math.randomseed(os.time()) -- cgit v1.2.3 From 7b21acd39745d265548eeb62d687cde9febb1f7a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 24 Aug 2024 10:37:57 -0400 Subject: Update test scripts to reference UI.Floater, not standalone Floater. --- indra/newview/scripts/lua/require/LLChatListener.lua | 2 +- indra/newview/scripts/lua/test_LLAppearance.lua | 4 ++-- indra/newview/scripts/lua/test_camera_control.lua | 4 ++-- indra/newview/scripts/lua/test_luafloater_demo.lua | 6 +++--- indra/newview/scripts/lua/test_luafloater_gesture_list.lua | 4 ++-- indra/newview/scripts/lua/test_luafloater_speedometer.lua | 3 +-- 6 files changed, 11 insertions(+), 12 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/require/LLChatListener.lua b/indra/newview/scripts/lua/require/LLChatListener.lua index 428dca881e..82b28966ce 100644 --- a/indra/newview/scripts/lua/require/LLChatListener.lua +++ b/indra/newview/scripts/lua/require/LLChatListener.lua @@ -23,7 +23,7 @@ function LLChatListener:handleMessages(event_data) end function LLChatListener:start() - waitfor = leap.WaitFor:new(-1, self.name) + waitfor = leap.WaitFor(-1, self.name) function waitfor:filter(pump, data) if pump == "LLNearbyChat" then return data diff --git a/indra/newview/scripts/lua/test_LLAppearance.lua b/indra/newview/scripts/lua/test_LLAppearance.lua index 5ddd9f15ff..a97ec4e0ca 100644 --- a/indra/newview/scripts/lua/test_LLAppearance.lua +++ b/indra/newview/scripts/lua/test_LLAppearance.lua @@ -1,7 +1,7 @@ -local Floater = require 'Floater' local LLAppearance = require 'LLAppearance' local startup = require 'startup' local inspect = require 'inspect' +local UI = require 'UI' local SHOW_OUTFITS = true local SELECTED_OUTFIT_ID = {} @@ -15,7 +15,7 @@ local outfits_title = 'Outfits' local wear_lbl = 'Wear item' local detach_lbl = 'Detach item' -local flt = Floater:new( +local flt = UI.Floater( "luafloater_outfits_list.xml", {outfits_list = {"double_click"}}) diff --git a/indra/newview/scripts/lua/test_camera_control.lua b/indra/newview/scripts/lua/test_camera_control.lua index 9b35cdf8cd..7ac0986ee6 100644 --- a/indra/newview/scripts/lua/test_camera_control.lua +++ b/indra/newview/scripts/lua/test_camera_control.lua @@ -1,8 +1,8 @@ -local Floater = require 'Floater' local LLAgent = require 'LLAgent' local startup = require 'startup' +local UI = require 'UI' -local flt = Floater('luafloater_camera_control.xml') +local flt = UI.Floater('luafloater_camera_control.xml') function getValue(ctrl_name) return flt:request({action="get_value", ctrl_name=ctrl_name}).value diff --git a/indra/newview/scripts/lua/test_luafloater_demo.lua b/indra/newview/scripts/lua/test_luafloater_demo.lua index 3903d01e65..2158134511 100644 --- a/indra/newview/scripts/lua/test_luafloater_demo.lua +++ b/indra/newview/scripts/lua/test_luafloater_demo.lua @@ -1,8 +1,8 @@ -local Floater = require 'Floater' local leap = require 'leap' local startup = require 'startup' +local UI = require 'UI' -local flt = Floater( +local flt = UI.Floater( 'luafloater_demo.xml', {show_time_lbl = {"right_mouse_down", "double_click"}}) @@ -10,7 +10,7 @@ local flt = Floater( function flt:handleEvents(event_data) self:post({action="add_text", ctrl_name="events_editor", value = event_data}) -- forward the call to base-class handleEvents() - return Floater.handleEvents(self, event_data) + return UI.Floater.handleEvents(self, event_data) end function flt:commit_disable_ctrl(event_data) diff --git a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua index bd397ef2a6..5f929c0d0c 100644 --- a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua +++ b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua @@ -1,8 +1,8 @@ -local Floater = require 'Floater' local LLGesture = require 'LLGesture' local startup = require 'startup' +local UI = require 'UI' -local flt = Floater( +local flt = UI.Floater( "luafloater_gesture_list.xml", {gesture_list = {"double_click"}}) diff --git a/indra/newview/scripts/lua/test_luafloater_speedometer.lua b/indra/newview/scripts/lua/test_luafloater_speedometer.lua index 8bc18ad286..2cdd41fd7e 100644 --- a/indra/newview/scripts/lua/test_luafloater_speedometer.lua +++ b/indra/newview/scripts/lua/test_luafloater_speedometer.lua @@ -1,11 +1,10 @@ -local Floater = require 'Floater' local leap = require 'leap' local startup = require 'startup' local Timer = (require 'timers').Timer local UI = require 'UI' local popup = UI.popup local max_speed = 0 -local flt = Floater("luafloater_speedometer.xml") +local flt = UI.Floater("luafloater_speedometer.xml") startup.wait('STATE_STARTED') local timer -- cgit v1.2.3 From 3cc48ac9e22fb562560929a5c45a8b6ef1ecc841 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Mon, 26 Aug 2024 13:48:12 +0300 Subject: clean up Lua prefix --- indra/newview/llviewermessage.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'indra') diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 4922e3c9f9..56f759833c 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -2646,15 +2646,19 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) else { chat.mText = ""; - std::string chat_text(mesg); auto [msg_without_prefix, is_lua] = LLStringUtil::withoutPrefix(mesg, LUA_PREFIX); + std::string prefix; + if (is_lua) + { + prefix = LUA_PREFIX; + } switch(chat.mChatType) { case CHAT_TYPE_WHISPER: + prefix += LLTrans::getString("whisper") + " "; + break; case CHAT_TYPE_SHOUT: - chat_text = LLTrans::getString(chat.mChatType == CHAT_TYPE_WHISPER ? "whisper" : "shout") + " " + msg_without_prefix; - if(is_lua) - chat_text = LUA_PREFIX + chat_text; + prefix += LLTrans::getString("shout") + " "; break; case CHAT_TYPE_DEBUG_MSG: case CHAT_TYPE_OWNER: @@ -2670,7 +2674,7 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) break; } - chat.mText += chat_text; + chat.mText = prefix + msg_without_prefix; } // We have a real utterance now, so can stop showing "..." and proceed. -- cgit v1.2.3 From 27ce2a23a286709c90579db31c2551e0c3f292e7 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Tue, 27 Aug 2024 16:27:34 +0300 Subject: code clean up --- indra/llcommon/llassettype.cpp | 4 +- indra/llinventory/llfoldertype.cpp | 7 +- indra/newview/llinventorylistener.cpp | 136 +++++++++++++--------- indra/newview/llinventorylistener.h | 28 ----- indra/newview/scripts/lua/require/LLInventory.lua | 12 +- indra/newview/scripts/lua/test_LLInventory.lua | 2 +- 6 files changed, 92 insertions(+), 97 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp index 1c0893b1bf..00c61c6e0a 100644 --- a/indra/llcommon/llassettype.cpp +++ b/indra/llcommon/llassettype.cpp @@ -250,9 +250,9 @@ LLSD LLAssetType::getTypeNames() { LLSD type_names; const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - for (S32 type = AT_TEXTURE; type < AT_COUNT; ++type) + for (S32 type = 0; type < AT_COUNT; ++type) { - const AssetEntry *entry = dict->lookup((LLAssetType::EType) type); + const AssetEntry *entry = dict->lookup(LLAssetType::EType(type)); // skip llassettype_bad_lookup if (entry) { diff --git a/indra/llinventory/llfoldertype.cpp b/indra/llinventory/llfoldertype.cpp index c8ef1ccc1b..95deb46431 100644 --- a/indra/llinventory/llfoldertype.cpp +++ b/indra/llinventory/llfoldertype.cpp @@ -225,11 +225,12 @@ const std::string &LLFolderType::badLookup() LLSD LLFolderType::getTypeNames() { LLSD type_names; - for (S32 type = FT_TEXTURE; type < FT_COUNT; ++type) + const LLFolderDictionary *dict = LLFolderDictionary::getInstance(); + for (S32 type = 0; type < FT_COUNT; ++type) { - if (lookupIsEnsembleType((LLFolderType::EType)type)) continue; + if (lookupIsEnsembleType(LLFolderType::EType(type))) continue; - const FolderEntry *entry = LLFolderDictionary::getInstance()->lookup((LLFolderType::EType)type); + const FolderEntry *entry = dict->lookup(LLFolderType::EType(type)); //skip llfoldertype_bad_lookup if (entry) { diff --git a/indra/newview/llinventorylistener.cpp b/indra/newview/llinventorylistener.cpp index 0aa1208d83..157e04dce3 100644 --- a/indra/newview/llinventorylistener.cpp +++ b/indra/newview/llinventorylistener.cpp @@ -40,18 +40,18 @@ LLInventoryListener::LLInventoryListener() "API for interactions with viewer Inventory items") { add("getItemsInfo", - "Return information about items or folders defined in [\"items_id\"]:\n" + "Return information about items or folders defined in [\"item_ids\"]:\n" "reply will contain [\"items\"] and [\"categories\"] tables accordingly", &LLInventoryListener::getItemsInfo, - llsd::map("items_id", LLSD(), "reply", LLSD())); + llsd::map("item_ids", LLSD(), "reply", LLSD())); add("getFolderTypeNames", - "Return the table of folder type names, contained in [\"type_names\"]\n", + "Return the table of folder type names, contained in [\"names\"]\n", &LLInventoryListener::getFolderTypeNames, llsd::map("reply", LLSD())); add("getAssetTypeNames", - "Return the table of asset type names, contained in [\"type_names\"]\n", + "Return the table of asset type names, contained in [\"names\"]\n", &LLInventoryListener::getAssetTypeNames, llsd::map("reply", LLSD())); @@ -70,8 +70,8 @@ LLInventoryListener::LLInventoryListener() "Return the descendents(both items and folders) of the [\"folder_id\"], if it passes specified filters:\n" "[\"name\"] is a substring of object's name,\n" "[\"desc\"] is a substring of object's description,\n" - "asset [\"type\"] corresponds to the object's asset type\n" - "[\"item_limit\"] sets item count limit in reply, maximum and default is 100\n" + "asset [\"type\"] corresponds to the string name of the object's asset type\n" + "[\"limit\"] sets item count limit in reply, maximum and default is 100\n" "[\"filter_links\"]: EXCLUDE_LINKS - don't show links, ONLY_LINKS - only show links, INCLUDE_LINKS - show links too (default)", &LLInventoryListener::collectDescendentsIf, llsd::map("folder_id", LLSD(), "reply", LLSD())); @@ -81,23 +81,42 @@ LLInventoryListener::LLInventoryListener() void add_item_info(LLEventAPI::Response& response, LLViewerInventoryItem* item) { response["items"].insert(item->getUUID().asString(), - llsd::map("name", item->getName(), "parent_id", item->getParentUUID(), "desc", item->getDescription(), - "inv_type", LLInventoryType::lookup(item->getInventoryType()), "asset_type", LLAssetType::lookup(item->getType()), - "creation_date", (S32) item->getCreationDate(), "asset_id", item->getAssetUUID(), - "is_link", item->getIsLinkType(), "linked_id", item->getLinkedUUID())); + llsd::map("name", item->getName(), + "parent_id", item->getParentUUID(), + "desc", item->getDescription(), + "inv_type", LLInventoryType::lookup(item->getInventoryType()), + "asset_type", LLAssetType::lookup(item->getType()), + "creation_date", (S32) item->getCreationDate(), + "asset_id", item->getAssetUUID(), + "is_link", item->getIsLinkType(), + "linked_id", item->getLinkedUUID())); } void add_cat_info(LLEventAPI::Response &response, LLViewerInventoryCategory *cat) { response["categories"].insert(cat->getUUID().asString(), - llsd::map("name", cat->getName(), "parent_id", cat->getParentUUID(), "type", LLFolderType::lookup(cat->getPreferredType()))); + llsd::map("name", cat->getName(), + "parent_id", cat->getParentUUID(), + "type", LLFolderType::lookup(cat->getPreferredType()))); +} + +void add_objects_info(LLEventAPI::Response& response, LLInventoryModel::cat_array_t cat_array, LLInventoryModel::item_array_t item_array) +{ + for (auto &p : item_array) + { + add_item_info(response, p); + } + for (auto &p : cat_array) + { + add_cat_info(response, p); + } } void LLInventoryListener::getItemsInfo(LLSD const &data) { Response response(LLSD(), data); - uuid_vec_t ids = LLSDParam(data["items_id"]); + uuid_vec_t ids = LLSDParam(data["item_ids"]); for (auto &it : ids) { LLViewerInventoryItem* item = gInventory.getItem(it); @@ -105,22 +124,25 @@ void LLInventoryListener::getItemsInfo(LLSD const &data) { add_item_info(response, item); } - LLViewerInventoryCategory* cat = gInventory.getCategory(it); - if (cat) + else { - add_cat_info(response, cat); + LLViewerInventoryCategory *cat = gInventory.getCategory(it); + if (cat) + { + add_cat_info(response, cat); + } } } } void LLInventoryListener::getFolderTypeNames(LLSD const &data) { - Response response(llsd::map("type_names", LLFolderType::getTypeNames()), data); + Response response(llsd::map("names", LLFolderType::getTypeNames()), data); } void LLInventoryListener::getAssetTypeNames(LLSD const &data) { - Response response(llsd::map("type_names", LLAssetType::getTypeNames()), data); + Response response(llsd::map("names", LLAssetType::getTypeNames()), data); } void LLInventoryListener::getBasicFolderID(LLSD const &data) @@ -136,18 +158,37 @@ void LLInventoryListener::getDirectDescendents(LLSD const &data) LLInventoryModel::item_array_t* items; gInventory.getDirectDescendentsOf(data["folder_id"], cats, items); - LLInventoryModel::item_array_t items_copy = *items; - for (LLInventoryModel::item_array_t::iterator iter = items_copy.begin(); iter != items_copy.end(); iter++) - { - add_item_info(response, *iter); - } - LLInventoryModel::cat_array_t cats_copy = *cats; - for (LLInventoryModel::cat_array_t::iterator iter = cats_copy.begin(); iter != cats_copy.end(); iter++) - { - add_cat_info(response, *iter); - } + add_objects_info(response, *cats, *items); } +struct LLFilteredCollector : public LLInventoryCollectFunctor +{ + enum EFilterLink + { + INCLUDE_LINKS, // show links too + EXCLUDE_LINKS, // don't show links + ONLY_LINKS // only show links + }; + + LLFilteredCollector(LLSD const &data); + virtual ~LLFilteredCollector() {} + virtual bool operator()(LLInventoryCategory *cat, LLInventoryItem *item) override; + virtual bool exceedsLimit() override { return (mItemLimit <= mItemCount); }; + + protected: + bool checkagainstType(LLInventoryCategory *cat, LLInventoryItem *item); + bool checkagainstNameDesc(LLInventoryCategory *cat, LLInventoryItem *item); + bool checkagainstLinks(LLInventoryCategory *cat, LLInventoryItem *item); + + LLAssetType::EType mType; + std::string mName; + std::string mDesc; + EFilterLink mLinkFilter; + + S32 mItemLimit; + S32 mItemCount; +}; + void LLInventoryListener::collectDescendentsIf(LLSD const &data) { Response response(LLSD(), data); @@ -164,14 +205,7 @@ void LLInventoryListener::collectDescendentsIf(LLSD const &data) gInventory.collectDescendentsIf(folder_id, cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH, collector); - for (LLInventoryModel::item_array_t::iterator iter = item_array.begin(); iter != item_array.end(); iter++) - { - add_item_info(response, *iter); - } - for (LLInventoryModel::cat_array_t::iterator iter = cat_array.begin(); iter != cat_array.end(); iter++) - { - add_cat_info(response, *iter); - } + add_objects_info(response, cat_array, item_array); } LLFilteredCollector::LLFilteredCollector(LLSD const &data) : @@ -180,14 +214,10 @@ LLFilteredCollector::LLFilteredCollector(LLSD const &data) : mItemLimit(MAX_ITEM_LIMIT), mItemCount(0) { - if (data.has("name")) - { - mName = data["name"].asString(); - } - if (data.has("desc")) - { - mDesc = data["desc"].asString(); - } + + mName = data["name"].asString(); + mDesc = data["desc"].asString(); + if (data.has("type")) { mType = LLAssetType::lookup(data["type"]); @@ -203,9 +233,9 @@ LLFilteredCollector::LLFilteredCollector(LLSD const &data) : mLinkFilter = ONLY_LINKS; } } - if (data.has("item_limit")) + if (data["limit"].isInteger()) { - mItemLimit = llclamp(data["item_limit"].asInteger(), 1, MAX_ITEM_LIMIT); + mItemLimit = llclamp(data["limit"].asInteger(), 1, MAX_ITEM_LIMIT); } } @@ -222,11 +252,6 @@ bool LLFilteredCollector::operator()(LLInventoryCategory *cat, LLInventoryItem * return passed; } -bool LLFilteredCollector::exceedsLimit() -{ - return (mItemLimit <= mItemCount); -} - bool LLFilteredCollector::checkagainstNameDesc(LLInventoryCategory *cat, LLInventoryItem *item) { std::string name, desc; @@ -239,10 +264,10 @@ bool LLFilteredCollector::checkagainstNameDesc(LLInventoryCategory *cat, LLInven if (item) { name = item->getName(); - passed = (mDesc.size() ? item->getDescription().find(mDesc) != std::string::npos : true); + passed = (mDesc.empty() || (item->getDescription().find(mDesc) != std::string::npos)); } - return passed && (mName.size() ? name.find(mName) != std::string::npos : true); + return passed && (mName.empty() || name.find(mName) != std::string::npos); } bool LLFilteredCollector::checkagainstType(LLInventoryCategory *cat, LLInventoryItem *item) @@ -251,12 +276,9 @@ bool LLFilteredCollector::checkagainstType(LLInventoryCategory *cat, LLInventory { return true; } - if (mType == LLAssetType::AT_CATEGORY) + if (cat && (mType == LLAssetType::AT_CATEGORY)) { - if (cat) - { - return true; - } + return true; } if (item && item->getType() == mType) { diff --git a/indra/newview/llinventorylistener.h b/indra/newview/llinventorylistener.h index 2c1eb2cb6d..5cbac2ca32 100644 --- a/indra/newview/llinventorylistener.h +++ b/indra/newview/llinventorylistener.h @@ -44,33 +44,5 @@ private: void collectDescendentsIf(LLSD const &data); }; -struct LLFilteredCollector : public LLInventoryCollectFunctor -{ - enum EFilterLink - { - INCLUDE_LINKS, // show links too - EXCLUDE_LINKS, // don't show links - ONLY_LINKS // only show links - }; - - LLFilteredCollector(LLSD const &data); - virtual ~LLFilteredCollector() {} - virtual bool operator()(LLInventoryCategory *cat, LLInventoryItem *item); - virtual bool exceedsLimit(); - - protected: - bool checkagainstType(LLInventoryCategory *cat, LLInventoryItem *item); - bool checkagainstNameDesc(LLInventoryCategory *cat, LLInventoryItem *item); - bool checkagainstLinks(LLInventoryCategory *cat, LLInventoryItem *item); - - LLAssetType::EType mType; - std::string mName; - std::string mDesc; - EFilterLink mLinkFilter; - - S32 mItemLimit; - S32 mItemCount; -}; - #endif // LL_LLINVENTORYLISTENER_H diff --git a/indra/newview/scripts/lua/require/LLInventory.lua b/indra/newview/scripts/lua/require/LLInventory.lua index b3f817da94..dd1b910250 100644 --- a/indra/newview/scripts/lua/require/LLInventory.lua +++ b/indra/newview/scripts/lua/require/LLInventory.lua @@ -5,13 +5,13 @@ local LLInventory = {} -- Get the items/folders info by provided IDs, -- reply will contain "items" and "categories" tables accordingly -function LLInventory.getItemsInfo(items_id) - return leap.request('LLInventory', {op = 'getItemsInfo', items_id=items_id}) +function LLInventory.getItemsInfo(item_ids) + return leap.request('LLInventory', {op = 'getItemsInfo', item_ids=item_ids}) end -- Get the table of folder type names, which can be later used to get the ID of the basic folders function LLInventory.getFolderTypeNames() - return leap.request('LLInventory', {op = 'getFolderTypeNames'}).type_names + return leap.request('LLInventory', {op = 'getFolderTypeNames'}).names end -- Get the UUID of the basic folder("Textures", "My outfits", "Sounds" etc.) by specified folder type name @@ -21,7 +21,7 @@ end -- Get the table of asset type names, which can be later used to get the specific items via LLInventory.collectDescendentsIf(...) function LLInventory.getAssetTypeNames() - return leap.request('LLInventory', {op = 'getAssetTypeNames'}).type_names + return leap.request('LLInventory', {op = 'getAssetTypeNames'}).names end -- Get the direct descendents of the 'folder_id' provided, @@ -36,10 +36,10 @@ end -- [, name] -- name (substring) -- [, desc] -- description (substring) -- [, type] -- asset type --- [, item_limit] -- item count limit in reply, maximum and default is 100 +-- [, limit] -- item count limit in reply, maximum and default is 100 -- [, filter_links]} -- EXCLUDE_LINKS - don't show links, ONLY_LINKS - only show links, INCLUDE_LINKS - show links too (default) function LLInventory.collectDescendentsIf(...) - local args = mapargs('folder_id,name,desc,type,filter_links,item_limit', ...) + local args = mapargs('folder_id,name,desc,type,filter_links,limit', ...) args.op = 'collectDescendentsIf' return leap.request('LLInventory', args) end diff --git a/indra/newview/scripts/lua/test_LLInventory.lua b/indra/newview/scripts/lua/test_LLInventory.lua index dc6eb243ad..107b0791d4 100644 --- a/indra/newview/scripts/lua/test_LLInventory.lua +++ b/indra/newview/scripts/lua/test_LLInventory.lua @@ -4,7 +4,7 @@ LLInventory = require 'LLInventory' -- Get 'My Landmarks' folder id (you can see all folder types via LLInventory.getFolderTypeNames()) my_landmarks_id = LLInventory.getBasicFolderID('landmark') -- Get 3 landmarks from the 'My Landmarks' folder (you can see all folder types via LLInventory.getAssetTypeNames()) -landmarks = LLInventory.collectDescendentsIf{folder_id=my_landmarks_id, type="landmark", item_limit=3} +landmarks = LLInventory.collectDescendentsIf{folder_id=my_landmarks_id, type="landmark", limit=3} print(inspect(landmarks)) -- Get 'Calling Cards' folder id -- cgit v1.2.3 From 841e19c1cb62341c10254e6f4bf992c0c19d27b8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 28 Aug 2024 15:07:14 -0400 Subject: Remove obsolete, unreferenced DESTRINGIZE(), DEWSTRINGIZE() macros. --- indra/llcommon/stringize.h | 12 ------------ 1 file changed, 12 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/stringize.h b/indra/llcommon/stringize.h index 63d44a7272..9604d912b5 100644 --- a/indra/llcommon/stringize.h +++ b/indra/llcommon/stringize.h @@ -190,16 +190,4 @@ void destringize_f(std::basic_string const & str, Functor const & f) f(in); } -/** - * DESTRINGIZE(str, item1 >> item2 >> item3 ...) effectively expands to the - * following: - * @code - * std::istringstream in(str); - * in >> item1 >> item2 >> item3 ... ; - * @endcode - */ -#define DESTRINGIZE(STR, EXPRESSION) (destringize_f((STR), [&](auto& in){in >> EXPRESSION;})) -// legacy name, just use DESTRINGIZE() going forward -#define DEWSTRINGIZE(STR, EXPRESSION) DESTRINGIZE(STR, EXPRESSION) - #endif /* ! defined(LL_STRINGIZE_H) */ -- cgit v1.2.3 From 14c8fc3768d978205bf17ffc1905c2772afbd434 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 28 Aug 2024 16:47:38 -0400 Subject: Add `LL.setdtor()` function to add a "destructor" to any Lua object. `setdtor('description', object, function)` returns a proxy userdata object referencing object and function. When the proxy is garbage-collected, or at the end of the script, its destructor calls `function(object)`. The original object may be retrieved as `proxy._target`, e.g. to pass it to the `table` library. The proxy also has a metatable with metamethods supporting arithmetic operations, string concatenation, length and table indexing. For other operations, retrieve `proxy._target`. (But don't assign to `proxy._target`. It will appear to work, in that subsequent references to `proxy._target` will retrieve the replacement object -- however, the destructor will still call `function(original object)`.) Fix bugs in `lua_setfieldv()`, `lua_rawgetfield()` and `lua_rawsetfield()`. Add C++ functions `lua_destroyuserdata()` to explicitly destroy a `lua_emplace()` userdata object, plus `lua_destroybounduserdata()`. The latter can bind such a userdata object as an upvalue to pass to `LL.atexit()`. Make `LL.help()` and `LL.leaphelp()` help text include the `LL.` prefix. --- indra/llcommon/lua_function.cpp | 325 ++++++++++++++++++++++++++++- indra/llcommon/lua_function.h | 18 +- indra/newview/scripts/lua/test_setdtor.lua | 62 ++++++ 3 files changed, 396 insertions(+), 9 deletions(-) create mode 100644 indra/newview/scripts/lua/test_setdtor.lua (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 880bc209f6..12cff89fbd 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -99,6 +99,34 @@ fsyspath source_path(lua_State* L) } // namespace lluau +/***************************************************************************** +* lua_destroyuserdata(), lua_destroybounduserdata() (see lua_emplace()) +*****************************************************************************/ +int lua_destroyuserdata(lua_State* L) +{ + // stack: lua_emplace() userdata to be destroyed + if (int tag; + lua_isuserdata(L, -1) && + (tag = lua_userdatatag(L, -1)) != 0) + { + auto dtor = lua_getuserdatadtor(L, tag); + // detach this userdata from the destructor with tag 'tag' + lua_setuserdatatag(L, -1, 0); + // now run the real destructor + dtor(L, lua_touserdata(L, -1)); + } + lua_settop(L, 0); + return 0; +} + +int lua_destroybounduserdata(lua_State *L) +{ + // called with no arguments -- push bound upvalue + lluau_checkstack(L, 1); + lua_pushvalue(L, lua_upvalueindex(1)); + return lua_destroyuserdata(L); +} + /***************************************************************************** * Lua <=> C++ conversions *****************************************************************************/ @@ -664,10 +692,10 @@ LuaListener& LuaState::obtainListener(lua_State* L) // At this point, one way or the other, the stack top should be (a Lua // userdata containing) our LuaListener. LuaListener* listener{ lua_toclass(L, -1) }; - // userdata objects created by lua_emplace() are bound on the atexit() - // queue, and are thus never garbage collected: they're destroyed only - // when ~LuaState() walks that queue. That's why we dare pop the userdata - // value off the stack while still depending on a pointer into its data. + // Since our LuaListener instance is stored in the Registry, it won't be + // garbage collected: it will be destroyed only when lua_close() clears + // out the Registry. That's why we dare pop the userdata value off the + // stack while still depending on a pointer into its data. lua_pop(L, 1); return *listener; } @@ -851,8 +879,8 @@ lua_function(check_stop, "check_stop(): ensure that a Lua script responds to vie * help() *****************************************************************************/ lua_function(help, - "help(): list viewer's Lua functions\n" - "help(function): show help string for specific function") + "LL.help(): list viewer's Lua functions\n" + "LL.help(function): show help string for specific function") { auto& luapump{ LLEventPumps::instance().obtain("lua output") }; const auto& [registry, lookup]{ LuaFunction::getRState() }; @@ -911,8 +939,8 @@ lua_function(help, *****************************************************************************/ lua_function( leaphelp, - "leaphelp(): list viewer's LEAP APIs\n" - "leaphelp(api): show help for specific api string name") + "LL.leaphelp(): list viewer's LEAP APIs\n" + "LL.leaphelp(api): show help for specific api string name") { LLSD request; int top{ lua_gettop(L) }; @@ -974,6 +1002,287 @@ lua_function( return 0; // void return } +/***************************************************************************** +* setdtor +*****************************************************************************/ +namespace { + +// proxy userdata object returned by setdtor() +struct setdtor_refs +{ + lua_State* L; + std::string desc; + // You can't directly store a Lua object in a C++ object, but you can + // create a Lua "reference" by storing the object in the Lua Registry and + // capturing its Registry index. + int objref; + int dtorref; + + setdtor_refs(lua_State* L, const std::string& desc, int objref, int dtorref): + L(L), + desc(desc), + objref(objref), + dtorref(dtorref) + {} + setdtor_refs(const setdtor_refs&) = delete; + setdtor_refs& operator=(const setdtor_refs&) = delete; + ~setdtor_refs(); + + static void push_metatable(lua_State* L); + static std::string binop(const std::string& name, const std::string& op); + static int meta__index(lua_State* L); +}; + +} // anonymous namespace + +lua_function( + setdtor, + "setdtor(desc, obj, dtorfunc) => proxy object referencing obj and dtorfunc.\n" + "When the returned proxy object is garbage-collected, or when the script\n" + "ends, call dtorfunc(obj). String desc is logged in the error message, if any.\n" + "Use the returned proxy object (or proxy._target) like obj.\n" + "obj won't be destroyed as long as the proxy exists; it's the proxy object's\n" + "lifespan that determines when dtorfunc(obj) will be called.") +{ + if (lua_gettop(L) != 3) + { + return lluau::error(L, "setdtor(desc, obj, dtor) requires exactly 3 arguments"); + } + // called with (desc, obj, dtor), returns proxy object + lua_checkdelta(L, -2); + lluau_checkstack(L, 3); // might get up to 6 stack entries + auto desc{ lua_tostdstring(L, 1) }; + // Get Lua "references" for each of the object and the dtor function. + int objref = lua_ref(L, 2); + int dtorref = lua_ref(L, 3); + // Having captured each of our parameters, discard them. + lua_settop(L, 0); + // Push our setdtor_refs userdata. Not only do we want to push it on L's + // stack, but setdtor_refs's constructor itself requires L. + lua_emplace(L, L, desc, objref, dtorref); + // stack: proxy (i.e. setdtor_refs userdata) + // have to set its metatable + lua_getfield(L, LUA_REGISTRYINDEX, "setdtor_meta"); + // stack: proxy, setdtor_meta (which might be nil) + if (lua_isnil(L, -1)) + { + // discard nil + lua_pop(L, 1); + // compile and push our forwarding metatable + setdtor_refs::push_metatable(L); + // stack: proxy, metatable + // duplicate metatable to save it + lua_pushvalue(L, -1); + // stack: proxy, metatable, metable + // save metatable for future calls + lua_setfield(L, LUA_REGISTRYINDEX, "setdtor_meta"); + // stack: proxy, metatable + } + // stack: proxy, metatable + lua_setmetatable(L, -2); + // stack: proxy + // Because ~setdtor_refs() necessarily uses the Lua stack, the Registry et + // al., we can't let a setdtor_refs instance be destroyed by lua_close(): + // the Lua environment will already be partially shut down. To destroy + // this new setdtor_refs instance BEFORE lua_close(), bind it with + // lua_destroybounduserdata() and register it with LL.atexit(). + // push (the entry point for) LL.atexit() + lua_pushcfunction(L, atexit_luasub::call, "LL.atexit()"); + // stack: proxy, atexit() + lua_pushvalue(L, -2); + // stack: proxy, atexit(), proxy + int tag = lua_userdatatag(L, -1); + // We don't have a lookup table to get from an int Lua userdata tag to the + // corresponding C++ typeinfo name string. We'll introduce one if we need + // it for debugging. But for this particular call, we happen to know it's + // always a setdtor_refs object. + lua_pushcclosure(L, lua_destroybounduserdata, + stringize("lua_destroybounduserdata<", tag, ">()").c_str(), + 1); + // stack: proxy, atexit(), lua_destroybounduserdata + // call atexit(): one argument, no results, let error propagate + lua_call(L, 1, 0); + // stack: proxy + return 1; +} + +namespace { + +void setdtor_refs::push_metatable(lua_State* L) +{ + lua_checkdelta(L, 1); + lluau_checkstack(L, 1); + // Ideally we want a metatable that forwards every operation on our + // setdtor_refs userdata proxy object to the original object. But the + // published C API doesn't include (e.g.) arithmetic operations on Lua + // objects, so in fact it's easier to express the desired metatable in Lua + // than in C++. We could make setdtor() depend on an external Lua module, + // but it seems less fragile to embed the Lua source code right here. + static const std::string setdtor_meta = stringize(R"-( + -- This metatable literal doesn't define __index() because that's + -- implemented in C++. We cannot, in Lua, peek into the setdtor_refs + -- userdata object to obtain objref, nor can we fetch Registry[objref]. + -- So our C++ __index() metamethod recognizes access to '_target' as a + -- reference to Registry[objref]. + -- The rest are defined per https://www.lua.org/manual/5.1/manual.html#2.8. + -- Luau supports destructors instead of __gc metamethod -- we rely on that! + -- We don't set __mode because our proxy is not a table. Real references + -- are stored in the wrapped table, so ITS __mode is what counts. + -- Initial definition of meta omits binary metamethods so they can bind the + -- metatable itself, as explained for binop() below. + local meta = { + __unm = function(arg) + return -arg._target + end, + __len = function(arg) + return #arg._target + end, + -- Comparison metamethods __eq(), __lt() and __le() are only called + -- when both operands have the same metamethod. For our purposes, that + -- means both operands are setdtor_refs userdata objects. + __eq = function(lhs, rhs) + return (lhs._target == rhs._target) + end, + __lt = function(lhs, rhs) + return (lhs._target < rhs._target) + end, + __le = function(lhs, rhs) + return (lhs._target <= rhs._target) + end, + __newindex = function(t, key, value) + t._target[key] = value + end, + __call = function(func, ...) + return func._target(...) + end, + __tostring = function(arg) + -- don't fret about arg._target's __tostring metamethod, + -- if any, because built-in tostring() deals with that + return tostring(arg._target) + end + } +)-", + binop("add", "+"), + binop("sub", "-"), + binop("mul", "*"), + binop("div", "/"), + binop("mod", "%"), + binop("pow", "^"), + binop("concat", ".."), +R"-( + return meta +)-"); + // only needed for debugging binop() +// LL_DEBUGS("Lua") << setdtor_meta << LL_ENDL; + + if (lluau::dostring(L, LL_PRETTY_FUNCTION, setdtor_meta) != LUA_OK) + { + // stack: error message string + lua_error(L); + } + llassert(lua_gettop(L) > 0); + llassert(lua_type(L, -1) == LUA_TTABLE); + // stack: Lua metatable compiled from setdtor_meta source + // Inject our C++ __index metamethod. + lua_rawsetfield(L, -1, "__index"sv, &setdtor_refs::meta__index); +} + +// In the definition of setdtor_meta above, binary arithmethic and +// concatenation metamethods are a little funny in that we don't know a +// priori which operand is the userdata with our metatable: the metamethod +// can be invoked either way. So every such metamethod must check, which +// leads to lots of redundancy. Hence this helper function. Call it a Lua +// macro. +std::string setdtor_refs::binop(const std::string& name, const std::string& op) +{ + return stringize( + " meta.__", name, " = function(lhs, rhs)\n" + " if getmetatable(lhs) == meta then\n" + " return lhs._target ", op, " rhs\n" + " else\n" + " return lhs ", op, " rhs._target\n" + " end\n" + " end\n"); +} + +// setdtor_refs __index() metamethod +int setdtor_refs::meta__index(lua_State* L) +{ + // called with (setdtor_refs userdata, key), returns retrieved object + lua_checkdelta(L, -1); + lluau_checkstack(L, 2); + // stack: proxy, key + // get ptr to the C++ struct data + auto ptr = lua_toclass(L, -2); + // meta__index() should NEVER be called with anything but setdtor_refs! + llassert(ptr); + // push the wrapped object + lua_getref(L, ptr->objref); + // stack: proxy, key, _target + // replace userdata with _target + lua_replace(L, -3); + // stack: _target, key + // Duplicate key because lua_tostring() converts number to string: + // if the key is (e.g.) 1, don't try to retrieve _target["1"]! + lua_pushvalue(L, -1); + // stack: _target, key, key + // recognize the special _target field + if (lua_tostdstring(L, -1) == "_target") + { + // okay, ditch both copies of "_target" string key + lua_pop(L, 2); + // stack: _target + } + else // any key but _target + { + // ditch stringized key + lua_pop(L, 1); + // stack: _target, key + // replace key with _target[key], invoking metamethod if any + lua_gettable(L, -2); + // stack: _target, _target[key] + // discard _target + lua_remove(L, -2); + // stack: _target[key] + } + return 1; +} + +// When Lua destroys a setdtor_refs userdata object, either from garbage +// collection or from LL.atexit(lua_destroybounduserdata), it's time to keep +// its promise to call the specified Lua destructor function with the +// specified Lua object. Of course we must also delete the captured +// "references" to both objects. +setdtor_refs::~setdtor_refs() +{ + lua_checkdelta(L); + lluau_checkstack(L, 2); + // push Registry[dtorref] + lua_getref(L, dtorref); + // push Registry[objref] + lua_getref(L, objref); + // free Registry[dtorref] + lua_unref(L, dtorref); + // free Registry[objref] + lua_unref(L, objref); + // call dtor(obj): one arg, no result, no error function + int rc = lua_pcall(L, 1, 0, 0); + if (rc != LUA_OK) + { + // TODO: we don't really want to propagate the error here. + // If this setdtor_refs instance is being destroyed by + // LL.atexit(), we want to continue cleanup. If it's being + // garbage-collected, the call is completely unpredictable from + // the consuming script's point of view. But what to do about this + // error?? For now, just log it. + LL_WARNS("Lua") << "setdtor(" << std::quoted(desc) << ") error: " + << lua_tostring(L, -1) << LL_ENDL; + lua_pop(L, 1); + } +} + +} // anonymous namespace + /***************************************************************************** * lua_what *****************************************************************************/ diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 6965e206ab..83abe8d71e 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -339,6 +339,7 @@ auto lua_getfieldv(lua_State* L, int index, const char* k) template auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value) { + index = lua_absindex(L, index); lua_checkdelta(L); lluau_checkstack(L, 1); lua_push(L, value); @@ -349,6 +350,7 @@ auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value) template auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) { + index = lua_absindex(L, index); lua_checkdelta(L); lluau_checkstack(L, 1); lua_pushlstring(L, k.data(), k.length()); @@ -361,6 +363,7 @@ auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) template void lua_rawsetfield(lua_State* L, int index, const std::string_view& k, const T& value) { + index = lua_absindex(L, index); lua_checkdelta(L); lluau_checkstack(L, 2); lua_pushlstring(L, k.data(), k.length()); @@ -459,7 +462,7 @@ DistinctInt TypeTag::value; * containing a newly-constructed C++ object T(args...). The userdata has a * Luau destructor guaranteeing that the new T instance is destroyed when the * userdata is garbage-collected, no later than when the LuaState is - * destroyed. + * destroyed. It may be destroyed explicitly by calling lua_destroyuserdata(). * * Usage: * lua_emplace(L, T constructor args...); @@ -511,6 +514,19 @@ T* lua_toclass(lua_State* L, int index) return static_cast(ptr); } +/** + * Call lua_destroyuserdata() with the doomed userdata on the stack top. + * It must have been created by lua_emplace(). + */ +int lua_destroyuserdata(lua_State* L); + +/** + * Call lua_pushcclosure(L, lua_destroybounduserdata, 1) with the target + * userdata on the stack top. When the resulting C closure is called with no + * arguments, the bound userdata is destroyed by lua_destroyuserdata(). + */ +int lua_destroybounduserdata(lua_State *L); + /***************************************************************************** * lua_what() *****************************************************************************/ diff --git a/indra/newview/scripts/lua/test_setdtor.lua b/indra/newview/scripts/lua/test_setdtor.lua new file mode 100644 index 0000000000..743c5168d0 --- /dev/null +++ b/indra/newview/scripts/lua/test_setdtor.lua @@ -0,0 +1,62 @@ +inspect = require 'inspect' + +print('initial setdtor') +bye = LL.setdtor('initial setdtor', 'Goodbye world!', print) + +print('arithmetic') +n = LL.setdtor('arithmetic', 11, print) +print("n =", n) +print("n._target =", n._target) +print("getmetatable(n) =", inspect(getmetatable(n))) +print("-n =", -n) +for i = 10, 12 do + -- Comparison metamethods are only called if both operands have the same + -- metamethod. + tempi = LL.setdtor('tempi', i, function(n) print('temp', i) end) + print(`n < {i}`, n < tempi) + print(`n <= {i}`, n <= tempi) + print(`n == {i}`, n == tempi) + print(`n ~= {i}`, n ~= tempi) + print(`n >= {i}`, n >= tempi) + print(`n > {i}`, n > tempi) +end +for i = 2, 3 do + print(`n + {i} =`, n + i) + print(`{i} + n =`, i + n) + print(`n - {i} =`, n - i) + print(`{i} - n =`, i - n) + print(`n * {i} =`, n * i) + print(`{i} * n =`, i * n) + print(`n / {i} =`, n / i) + print(`{i} / n =`, i / n) + print(`n % {i} =`, n % i) + print(`{i} % n =`, i % n) + print(`n ^ {i} =`, n ^ i) + print(`{i} ^ n =`, i ^ n) +end + +print('string') +s = LL.setdtor('string', 'hello', print) +print('s =', s) +print('#s =', #s) +print('s .. " world" =', s .. " world") +print('"world " .. s =', "world " .. s) + +print('table') +t = LL.setdtor('table', {'[1]', '[2]', abc='.abc', def='.def'}, + function(t) print(inspect(t)) end) +print('t =', inspect(t)) +print('t._target =', inspect(t._target)) +print('#t =', #t) +print('t[2] =', t[2]) +print('t.def =', t.def) +t[1] = 'new [1]' +print('t[1] =', t[1]) + +print('function') +f = LL.setdtor('function', function(a, b) return (a .. b) end, print) +print('f =', f) +print('f._target =', f._target) +print('f("Hello", " world") =', f("Hello", " world")) + +print('cleanup') -- cgit v1.2.3 From 364ea79ab3a4d48e0d10fbeabb9b8e88f226baac Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 28 Aug 2024 19:34:05 -0400 Subject: Prevent erroneous assignment to LL.setdtor() proxy._target field. Trim redundant output from test_setdtor.lua. --- indra/llcommon/lua_function.cpp | 4 +++- indra/newview/scripts/lua/test_setdtor.lua | 28 ++++++++++++++-------------- 2 files changed, 17 insertions(+), 15 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 12cff89fbd..f61cf3fe10 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -1050,7 +1050,7 @@ lua_function( } // called with (desc, obj, dtor), returns proxy object lua_checkdelta(L, -2); - lluau_checkstack(L, 3); // might get up to 6 stack entries +// lluau_checkstack(L, 0); // might get up to 3 stack entries auto desc{ lua_tostdstring(L, 1) }; // Get Lua "references" for each of the object and the dtor function. int objref = lua_ref(L, 2); @@ -1150,6 +1150,8 @@ void setdtor_refs::push_metatable(lua_State* L) return (lhs._target <= rhs._target) end, __newindex = function(t, key, value) + assert(key ~= '_target', + "Don't try to replace a setdtor() proxy's _target") t._target[key] = value end, __call = function(func, ...) diff --git a/indra/newview/scripts/lua/test_setdtor.lua b/indra/newview/scripts/lua/test_setdtor.lua index 743c5168d0..61ed86dcc8 100644 --- a/indra/newview/scripts/lua/test_setdtor.lua +++ b/indra/newview/scripts/lua/test_setdtor.lua @@ -7,6 +7,7 @@ print('arithmetic') n = LL.setdtor('arithmetic', 11, print) print("n =", n) print("n._target =", n._target) +print(pcall(function() n._target = 12 end)) print("getmetatable(n) =", inspect(getmetatable(n))) print("-n =", -n) for i = 10, 12 do @@ -20,20 +21,19 @@ for i = 10, 12 do print(`n >= {i}`, n >= tempi) print(`n > {i}`, n > tempi) end -for i = 2, 3 do - print(`n + {i} =`, n + i) - print(`{i} + n =`, i + n) - print(`n - {i} =`, n - i) - print(`{i} - n =`, i - n) - print(`n * {i} =`, n * i) - print(`{i} * n =`, i * n) - print(`n / {i} =`, n / i) - print(`{i} / n =`, i / n) - print(`n % {i} =`, n % i) - print(`{i} % n =`, i % n) - print(`n ^ {i} =`, n ^ i) - print(`{i} ^ n =`, i ^ n) -end +i = 2 +print(`n + {i} =`, n + i) +print(`{i} + n =`, i + n) +print(`n - {i} =`, n - i) +print(`{i} - n =`, i - n) +print(`n * {i} =`, n * i) +print(`{i} * n =`, i * n) +print(`n / {i} =`, n / i) +print(`{i} / n =`, i / n) +print(`n % {i} =`, n % i) +print(`{i} % n =`, i % n) +print(`n ^ {i} =`, n ^ i) +print(`{i} ^ n =`, i ^ n) print('string') s = LL.setdtor('string', 'hello', print) -- cgit v1.2.3 From baa7255687f265b2996754490083e7c26c646d72 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 28 Aug 2024 20:50:33 -0400 Subject: Add script control to "Inventory.DoCreate" registered menu action. --- indra/newview/llinventorygallerymenu.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/llinventorygallerymenu.cpp b/indra/newview/llinventorygallerymenu.cpp index 37e2228b2e..2845c5b614 100644 --- a/indra/newview/llinventorygallerymenu.cpp +++ b/indra/newview/llinventorygallerymenu.cpp @@ -106,7 +106,8 @@ LLContextMenu* LLInventoryGalleryContextMenu::createMenu() { mGallery->doCreate(mUUIDs.front(), data); } - }); + }, + LLUICtrl::cb_info::UNTRUSTED_BLOCK); std::set uuids(mUUIDs.begin(), mUUIDs.end()); registrar.add("Inventory.Share", boost::bind(&LLAvatarActions::shareWithAvatars, uuids, gFloaterView->getParentFloater(mGallery)), LLUICtrl::cb_info::UNTRUSTED_BLOCK); -- cgit v1.2.3 From 03d7f2b84daf9ab991de6cad7d6149abda1ef716 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 28 Aug 2024 21:16:56 -0400 Subject: Ditch trailing spaces. --- indra/cmake/run_build_test.py | 2 +- indra/llcommon/coro_scheduler.cpp | 2 +- indra/llcommon/coro_scheduler.h | 2 +- indra/llcommon/fsyspath.h | 4 +- indra/llcommon/hexdump.h | 2 +- indra/llcommon/llcoros.h | 12 ++-- indra/llcommon/lleventcoro.cpp | 10 ++-- indra/llcommon/lleventdispatcher.h | 28 ++++----- indra/llcommon/llevents.cpp | 26 ++++---- indra/llcommon/llexception.h | 2 +- indra/llcommon/llformat.h | 10 ++-- indra/llcommon/llinstancetracker.h | 28 ++++----- indra/llcommon/llleap.cpp | 2 +- indra/llcommon/llleaplistener.cpp | 2 +- indra/llcommon/llleaplistener.h | 2 +- indra/llcommon/lockstatic.cpp | 2 +- indra/llcommon/lua_function.cpp | 6 +- indra/llcommon/lua_function.h | 2 +- indra/llcommon/lualistener.cpp | 2 +- indra/llcommon/lualistener.h | 2 +- indra/llcommon/scope_exit.h | 2 +- indra/llcommon/stringize.h | 10 ++-- indra/llcommon/tempset.h | 2 +- indra/llcommon/tests/StringVec.h | 2 +- indra/llcommon/tests/lleventcoro_test.cpp | 10 ++-- indra/llcommon/tests/lleventdispatcher_test.cpp | 4 +- indra/llcommon/tests/lleventfilter_test.cpp | 10 ++-- indra/llcommon/tests/llleap_test.cpp | 2 +- indra/llcommon/threadpool.cpp | 2 +- indra/llcommon/threadpool.h | 2 +- indra/llcommon/throttle.cpp | 2 +- indra/llcommon/throttle.h | 2 +- indra/llcommon/workqueue.cpp | 2 +- indra/llcommon/workqueue.h | 2 +- indra/llui/llfloaterreglistener.cpp | 10 ++-- indra/llui/llfloaterreglistener.h | 10 ++-- indra/llui/llluafloater.cpp | 80 ++++++++++++------------- indra/llui/llluafloater.h | 10 ++-- indra/llui/llmenugl.h | 2 +- indra/llui/lluictrl.cpp | 2 +- indra/newview/llappearancemgr.cpp | 2 +- indra/newview/llappearancemgr.h | 2 +- indra/newview/llfloatergodtools.cpp | 2 +- indra/newview/llfloaterluadebug.h | 12 ++-- indra/newview/llfloaterluascripts.cpp | 22 +++---- indra/newview/llfloaterluascripts.h | 12 ++-- indra/newview/llinventorygallerymenu.cpp | 4 +- indra/newview/llinventorylistener.cpp | 2 +- indra/newview/llluamanager.cpp | 14 ++--- indra/newview/llluamanager.h | 16 ++--- indra/newview/llpanelmaininventory.cpp | 2 +- indra/newview/llpanelobjectinventory.cpp | 4 +- indra/newview/lluilistener.cpp | 16 ++--- indra/newview/lluilistener.h | 10 ++-- indra/newview/llviewerchat.cpp | 2 +- indra/newview/llvoavatar.cpp | 2 +- indra/newview/tests/llluamanager_test.cpp | 2 +- indra/test/debug.h | 10 ++-- indra/test/print.h | 2 +- indra/test/writestr.h | 2 +- 60 files changed, 228 insertions(+), 228 deletions(-) (limited to 'indra') diff --git a/indra/cmake/run_build_test.py b/indra/cmake/run_build_test.py index ef4d712254..312d791d67 100755 --- a/indra/cmake/run_build_test.py +++ b/indra/cmake/run_build_test.py @@ -150,7 +150,7 @@ def translate_rc(rc): """ if rc is None: return "still running" - + if rc >= 0: return "terminated with rc %s" % rc diff --git a/indra/llcommon/coro_scheduler.cpp b/indra/llcommon/coro_scheduler.cpp index 337162cbd5..393356a39b 100644 --- a/indra/llcommon/coro_scheduler.cpp +++ b/indra/llcommon/coro_scheduler.cpp @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2024-08-05 * @brief Implementation for llcoro::scheduler. - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/coro_scheduler.h b/indra/llcommon/coro_scheduler.h index a7572ccf4d..cc7e75d798 100644 --- a/indra/llcommon/coro_scheduler.h +++ b/indra/llcommon/coro_scheduler.h @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2024-08-05 * @brief Custom scheduler for viewer's Boost.Fibers (aka coroutines) - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/fsyspath.h b/indra/llcommon/fsyspath.h index 3c749d84de..1b4aec09b4 100644 --- a/indra/llcommon/fsyspath.h +++ b/indra/llcommon/fsyspath.h @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2024-04-03 * @brief Adapt our UTF-8 std::strings for std::filesystem::path - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ @@ -20,7 +20,7 @@ // ... the method of conversion to the native character set depends on the // character type used by source. -// +// // * If the source character type is char, the encoding of the source is // assumed to be the native narrow encoding (so no conversion takes place on // POSIX systems). diff --git a/indra/llcommon/hexdump.h b/indra/llcommon/hexdump.h index 234168cd61..ab5ba2b16d 100755 --- a/indra/llcommon/hexdump.h +++ b/indra/llcommon/hexdump.h @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2023-10-03 * @brief iostream manipulators to stream hex, or string with nonprinting chars - * + * * $LicenseInfo:firstyear=2023&license=viewerlgpl$ * Copyright (c) 2023, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h index 7491a4123a..f26ed882e8 100644 --- a/indra/llcommon/llcoros.h +++ b/indra/llcommon/llcoros.h @@ -3,25 +3,25 @@ * @author Nat Goodspeed * @date 2009-06-02 * @brief Manage running boost::coroutine instances - * + * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -169,7 +169,7 @@ public: * LLCoros::launch()). */ static std::string getName(); - + /** * rethrow() is called by the thread's main fiber to propagate an * exception from any coroutine into the main fiber, where it can engage diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index d651aae39c..33db27a116 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -3,25 +3,25 @@ * @author Nat Goodspeed * @date 2009-04-29 * @brief Implementation for lleventcoro. - * + * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 698412fdb4..6b5524e1eb 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -6,25 +6,25 @@ * useful when you have a single LLEventPump listener on which you can * request different operations, vs. instantiating a different * LLEventPump for each such operation. - * + * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -201,7 +201,7 @@ public: template ::type, LLSD>::value - >::type> + >::type> void add(const std::string& name, const std::string& desc, R (CLASS::*method)(ARG)) @@ -213,7 +213,7 @@ public: template ::type, LLSD>::value - >::type> + >::type> void add(const std::string& name, const std::string& desc, R (CLASS::*method)(ARG) const) @@ -226,7 +226,7 @@ public: template ::type, LLSD>::value - >::type> + >::type> void add(const std::string& name, const std::string& desc, void (CLASS::*method)(ARG)) @@ -238,7 +238,7 @@ public: template ::type, LLSD>::value - >::type> + >::type> void add(const std::string& name, const std::string& desc, void (CLASS::*method)(ARG) const) @@ -247,7 +247,7 @@ public: } // non-const binary (or more) method - template + template void add(const std::string& name, const std::string& desc, R (CLASS::*method)(ARG0, ARG1, ARGS...)) @@ -256,7 +256,7 @@ public: } // const binary (or more) method - template + template void add(const std::string& name, const std::string& desc, R (CLASS::*method)(ARG0, ARG1, ARGS...) const) @@ -265,7 +265,7 @@ public: } // non-const binary (or more) method returning void - template + template void add(const std::string& name, const std::string& desc, void (CLASS::*method)(ARG0, ARG1, ARGS...)) @@ -274,7 +274,7 @@ public: } // const binary (or more) method returning void - template + template void add(const std::string& name, const std::string& desc, void (CLASS::*method)(ARG0, ARG1, ARGS...) const) @@ -371,7 +371,7 @@ public: const InstanceGetter& getter, const LLSD& params, const LLSD& defaults=LLSD()); - //@} + //@} /// Unregister a callable bool remove(const std::string& name); diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index 0338fd53e5..a694f0dc7f 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -3,25 +3,25 @@ * @author Nat Goodspeed * @date 2008-09-12 * @brief Implementation for llevents. - * + * * $LicenseInfo:firstyear=2008&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -430,8 +430,8 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLAwareL float nodePosition = 1.0; - // if the supplied name is empty we are not interested in the ordering mechanism - // and can bypass attempting to find the optimal location to insert the new + // if the supplied name is empty we are not interested in the ordering mechanism + // and can bypass attempting to find the optimal location to insert the new // listener. We'll just tack it on to the end. if (!name.empty()) // should be the same as testing against ANONYMOUS { @@ -576,12 +576,12 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLAwareL // Now that newNode has a value that places it appropriately in mSignal, // connect it. LLBoundListener bound = mSignal->connect_extended(nodePosition, listener); - + if (!name.empty()) { // note that we are not tracking anonymous listeners here either. - // This means that it is the caller's responsibility to either assign - // to a TempBoundListerer (scoped_connection) or manually disconnect - // when done. + // This means that it is the caller's responsibility to either assign + // to a TempBoundListerer (scoped_connection) or manually disconnect + // when done. mConnections[name] = bound; } return bound; @@ -648,9 +648,9 @@ bool LLEventMailDrop::post(const LLSD& event) { // forward the call to our base class bool posted = LLEventStream::post(event); - + if (!posted) - { // if the event was not handled we will save it for later so that it can + { // if the event was not handled we will save it for later so that it can // be posted to any future listeners when they attach. mEventHistory.push_back(event); } diff --git a/indra/llcommon/llexception.h b/indra/llcommon/llexception.h index 9e322db86d..15dacd76fc 100644 --- a/indra/llcommon/llexception.h +++ b/indra/llcommon/llexception.h @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2016-06-29 * @brief Types needed for generic exception handling - * + * * $LicenseInfo:firstyear=2016&license=viewerlgpl$ * Copyright (c) 2016, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/llformat.h b/indra/llcommon/llformat.h index 97ea3b7b78..0c840d6e8e 100644 --- a/indra/llcommon/llformat.h +++ b/indra/llcommon/llformat.h @@ -1,4 +1,4 @@ -/** +/** * @file llformat.h * @date January 2007 * @brief string formatting utility @@ -6,21 +6,21 @@ * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index aba9f1187b..78582eac43 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -1,4 +1,4 @@ -/** +/** * @file llinstancetracker.h * @brief LLInstanceTracker is a mixin class that automatically tracks object * instances with or without an associated key @@ -6,21 +6,21 @@ * $LicenseInfo:firstyear=2000&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -101,11 +101,11 @@ public: return mSelf; } - static size_t instanceCount() - { - return LockStatic()->mMap.size(); + static size_t instanceCount() + { + return LockStatic()->mMap.size(); } - + // snapshot of std::pair> pairs, for // some SUBCLASS derived from T template @@ -229,7 +229,7 @@ public: } protected: - LLInstanceTracker(const KEY& key) + LLInstanceTracker(const KEY& key) { // We do not intend to manage the lifespan of this object with // shared_ptr, so give it a no-op deleter. We store shared_ptrs in our @@ -301,9 +301,9 @@ private: static std::string report(const char* key) { return report(std::string(key)); } // caller must instantiate LockStatic - void add_(LockStatic& lock, const KEY& key, const ptr_t& ptr) - { - mInstanceKey = key; + void add_(LockStatic& lock, const KEY& key, const ptr_t& ptr) + { + mInstanceKey = key; InstanceMap& map = lock->mMap; switch(KEY_COLLISION_BEHAVIOR) { @@ -389,7 +389,7 @@ public: { return mSelf; } - + static size_t instanceCount() { return LockStatic()->mSet.size(); diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index ccd27613ae..306d4e48d0 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2012-02-20 * @brief Implementation for llleap. - * + * * $LicenseInfo:firstyear=2012&license=viewerlgpl$ * Copyright (c) 2012, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp index 9b9b0f5121..b81ee66ba9 100644 --- a/indra/llcommon/llleaplistener.cpp +++ b/indra/llcommon/llleaplistener.cpp @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2012-03-16 * @brief Implementation for llleaplistener. - * + * * $LicenseInfo:firstyear=2012&license=viewerlgpl$ * Copyright (c) 2012, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/llleaplistener.h b/indra/llcommon/llleaplistener.h index 040fb737b7..d36d2ff8db 100644 --- a/indra/llcommon/llleaplistener.h +++ b/indra/llcommon/llleaplistener.h @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2012-03-16 * @brief LLEventAPI supporting LEAP plugins - * + * * $LicenseInfo:firstyear=2012&license=viewerlgpl$ * Copyright (c) 2012, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/lockstatic.cpp b/indra/llcommon/lockstatic.cpp index b647208724..f531ef331e 100755 --- a/indra/llcommon/lockstatic.cpp +++ b/indra/llcommon/lockstatic.cpp @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2024-05-23 * @brief Implementation for lockstatic. - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 880bc209f6..ffb90032d2 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2024-02-05 * @brief Implementation for lua_function. - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ @@ -700,11 +700,11 @@ void LuaState::check_interrupts_counter() // of interrupting itself at a moment when re-entry is not valid. So only // touch data in this LuaState. ++mInterrupts; - if (mInterrupts > INTERRUPTS_MAX_LIMIT) + if (mInterrupts > INTERRUPTS_MAX_LIMIT) { lluau::error(mState, "Possible infinite loop, terminated."); } - else if (mInterrupts % INTERRUPTS_SUSPEND_LIMIT == 0) + else if (mInterrupts % INTERRUPTS_SUSPEND_LIMIT == 0) { LL_DEBUGS("Lua") << LLCoros::getName() << " suspending at " << mInterrupts << " interrupts" << LL_ENDL; diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 6965e206ab..b9d640f84f 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2024-02-05 * @brief Definitions useful for coding a new Luau entry point into C++ - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/lualistener.cpp b/indra/llcommon/lualistener.cpp index 6cb87e8af2..94085c6798 100644 --- a/indra/llcommon/lualistener.cpp +++ b/indra/llcommon/lualistener.cpp @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2024-02-06 * @brief Implementation for lualistener. - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/lualistener.h b/indra/llcommon/lualistener.h index 68131dfa27..4c0a2a5c87 100644 --- a/indra/llcommon/lualistener.h +++ b/indra/llcommon/lualistener.h @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2024-02-06 * @brief Define LuaListener class - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/scope_exit.h b/indra/llcommon/scope_exit.h index 00fab069c4..eb4df4f74d 100644 --- a/indra/llcommon/scope_exit.h +++ b/indra/llcommon/scope_exit.h @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2024-08-15 * @brief Cheap imitation of std::experimental::scope_exit - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/stringize.h b/indra/llcommon/stringize.h index 63d44a7272..e712b6cc28 100644 --- a/indra/llcommon/stringize.h +++ b/indra/llcommon/stringize.h @@ -3,25 +3,25 @@ * @author Nat Goodspeed * @date 2008-12-17 * @brief stringize(item) template function and STRINGIZE(expression) macro - * + * * $LicenseInfo:firstyear=2008&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ diff --git a/indra/llcommon/tempset.h b/indra/llcommon/tempset.h index 07e607a576..773f19f756 100755 --- a/indra/llcommon/tempset.h +++ b/indra/llcommon/tempset.h @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2024-06-12 * @brief Temporarily override a variable for scope duration, then restore - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/tests/StringVec.h b/indra/llcommon/tests/StringVec.h index 761956a012..3f8665affb 100644 --- a/indra/llcommon/tests/StringVec.h +++ b/indra/llcommon/tests/StringVec.h @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2012-02-24 * @brief Extend TUT ensure_equals() to handle std::vector - * + * * $LicenseInfo:firstyear=2012&license=viewerlgpl$ * Copyright (c) 2012, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp index e7674fde37..cd3bbb21df 100644 --- a/indra/llcommon/tests/lleventcoro_test.cpp +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -3,25 +3,25 @@ * @author Nat Goodspeed * @date 2009-04-22 * @brief Test for coroutine. - * + * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 42d752514b..be05ca4e85 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2011-01-20 * @brief Test for lleventdispatcher. - * + * * $LicenseInfo:firstyear=2011&license=viewerlgpl$ * Copyright (c) 2011, Linden Research, Inc. * $/LicenseInfo$ @@ -470,7 +470,7 @@ namespace tut params["a"], "\n" "params[\"b\"]:\n", params["b"]); - // default LLSD::Binary value + // default LLSD::Binary value std::vector binary; for (size_t ix = 0, h = 0xaa; ix < 6; ++ix, h += 0x11) { diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp index a3d55d0cc6..e1f41faa44 100644 --- a/indra/llcommon/tests/lleventfilter_test.cpp +++ b/indra/llcommon/tests/lleventfilter_test.cpp @@ -3,25 +3,25 @@ * @author Nat Goodspeed * @date 2009-03-06 * @brief Test for lleventfilter. - * + * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index a7661cc7d8..ca1939c81e 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2012-02-21 * @brief Test for llleap. - * + * * $LicenseInfo:firstyear=2012&license=viewerlgpl$ * Copyright (c) 2012, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp index 8c0217de04..c94b5eecce 100644 --- a/indra/llcommon/threadpool.cpp +++ b/indra/llcommon/threadpool.cpp @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2021-10-21 * @brief Implementation for threadpool. - * + * * $LicenseInfo:firstyear=2021&license=viewerlgpl$ * Copyright (c) 2021, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/threadpool.h b/indra/llcommon/threadpool.h index 7ced7fbf9f..2748d7b073 100644 --- a/indra/llcommon/threadpool.h +++ b/indra/llcommon/threadpool.h @@ -4,7 +4,7 @@ * @date 2021-10-21 * @brief ThreadPool configures a WorkQueue along with a pool of threads to * service it. - * + * * $LicenseInfo:firstyear=2021&license=viewerlgpl$ * Copyright (c) 2021, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/throttle.cpp b/indra/llcommon/throttle.cpp index d5f7d5dcab..deb0a26fb0 100644 --- a/indra/llcommon/throttle.cpp +++ b/indra/llcommon/throttle.cpp @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2024-08-12 * @brief Implementation for ThrottleBase. - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/throttle.h b/indra/llcommon/throttle.h index 481d1d8f72..7f5389f464 100644 --- a/indra/llcommon/throttle.h +++ b/indra/llcommon/throttle.h @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2024-08-12 * @brief Throttle class - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp index 800547084a..9138c862f9 100644 --- a/indra/llcommon/workqueue.cpp +++ b/indra/llcommon/workqueue.cpp @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2021-10-06 * @brief Implementation for WorkQueue. - * + * * $LicenseInfo:firstyear=2021&license=viewerlgpl$ * Copyright (c) 2021, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h index 0581f85bfa..eb6923df0b 100644 --- a/indra/llcommon/workqueue.h +++ b/indra/llcommon/workqueue.h @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2021-09-30 * @brief Queue used for inter-thread work passing. - * + * * $LicenseInfo:firstyear=2021&license=viewerlgpl$ * Copyright (c) 2021, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llui/llfloaterreglistener.cpp b/indra/llui/llfloaterreglistener.cpp index e17f9f4dd6..b0dceb55c8 100644 --- a/indra/llui/llfloaterreglistener.cpp +++ b/indra/llui/llfloaterreglistener.cpp @@ -3,25 +3,25 @@ * @author Nat Goodspeed * @date 2009-08-12 * @brief Implementation for llfloaterreglistener. - * + * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ diff --git a/indra/llui/llfloaterreglistener.h b/indra/llui/llfloaterreglistener.h index 2165b1b62f..42e7178cbc 100644 --- a/indra/llui/llfloaterreglistener.h +++ b/indra/llui/llfloaterreglistener.h @@ -3,25 +3,25 @@ * @author Nat Goodspeed * @date 2009-08-12 * @brief Wrap (subset of) LLFloaterReg API with an event API - * + * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ diff --git a/indra/llui/llluafloater.cpp b/indra/llui/llluafloater.cpp index b08508bf9b..ca80a2c451 100644 --- a/indra/llui/llluafloater.cpp +++ b/indra/llui/llluafloater.cpp @@ -1,24 +1,24 @@ -/** +/** * @file llluafloater.cpp * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2024, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ * @@ -68,25 +68,25 @@ LLLuaFloater::LLLuaFloater(const LLSD &key) : }; LLSD requiredParams = llsd::map("ctrl_name", LLSD(), "value", LLSD()); - mDispatchListener.add("set_enabled", "", [ctrl_lookup](const LLSD &event) - { + mDispatchListener.add("set_enabled", "", [ctrl_lookup](const LLSD &event) + { return ctrl_lookup(event, [](LLUICtrl *ctrl, const LLSD &event) { ctrl->setEnabled(event["value"].asBoolean()); return LLSD(); }); }, requiredParams); - mDispatchListener.add("set_visible", "", [ctrl_lookup](const LLSD &event) - { + mDispatchListener.add("set_visible", "", [ctrl_lookup](const LLSD &event) + { return ctrl_lookup(event, [](LLUICtrl *ctrl, const LLSD &event) { ctrl->setVisible(event["value"].asBoolean()); return LLSD(); }); }, requiredParams); - mDispatchListener.add("set_value", "", [ctrl_lookup](const LLSD &event) - { + mDispatchListener.add("set_value", "", [ctrl_lookup](const LLSD &event) + { return ctrl_lookup(event, [](LLUICtrl *ctrl, const LLSD &event) { ctrl->setValue(event["value"]); return LLSD(); }); }, requiredParams); - mDispatchListener.add("add_list_element", "", [this](const LLSD &event) - { + mDispatchListener.add("add_list_element", "", [this](const LLSD &event) + { LLScrollListCtrl *ctrl = getChild(event["ctrl_name"].asString()); - if(ctrl) + if(ctrl) { - LLSD element_data = event["value"]; + LLSD element_data = event["value"]; if (element_data.isArray()) { for (const auto &row : llsd::inArray(element_data)) @@ -94,34 +94,34 @@ LLLuaFloater::LLLuaFloater(const LLSD &key) : ctrl->addElement(row); } } - else + else { ctrl->addElement(element_data); } } }, requiredParams); - mDispatchListener.add("clear_list", "", [this](const LLSD &event) - { + mDispatchListener.add("clear_list", "", [this](const LLSD &event) + { LLScrollListCtrl *ctrl = getChild(event["ctrl_name"].asString()); - if(ctrl) + if(ctrl) { ctrl->deleteAllItems(); } }, llsd::map("ctrl_name", LLSD())); - mDispatchListener.add("add_text", "", [this](const LLSD &event) - { + mDispatchListener.add("add_text", "", [this](const LLSD &event) + { LLTextEditor *editor = getChild(event["ctrl_name"].asString()); - if (editor) + if (editor) { editor->pasteTextWithLinebreaks(stringize(event["value"])); editor->addLineBreakChar(true); } }, requiredParams); - mDispatchListener.add("set_label", "", [this](const LLSD &event) - { + mDispatchListener.add("set_label", "", [this](const LLSD &event) + { LLButton *btn = getChild(event["ctrl_name"].asString()); if (btn) { @@ -129,17 +129,17 @@ LLLuaFloater::LLLuaFloater(const LLSD &key) : } }, requiredParams); - mDispatchListener.add("set_title", "", [this](const LLSD &event) - { + mDispatchListener.add("set_title", "", [this](const LLSD &event) + { setTitle(event["value"].asString()); }, llsd::map("value", LLSD())); - - mDispatchListener.add("get_value", "", [ctrl_lookup](const LLSD &event) - { + + mDispatchListener.add("get_value", "", [ctrl_lookup](const LLSD &event) + { return ctrl_lookup(event, [](LLUICtrl *ctrl, const LLSD &event) { return llsd::map("value", ctrl->getValue()); }); }, llsd::map("ctrl_name", LLSD(), "reqid", LLSD())); - mDispatchListener.add("get_selected_id", "", [this](const LLSD &event) + mDispatchListener.add("get_selected_id", "", [this](const LLSD &event) { LLScrollListCtrl *ctrl = getChild(event["ctrl_name"].asString()); if (!ctrl) @@ -157,7 +157,7 @@ LLLuaFloater::~LLLuaFloater() post(LLSD()); } -BOOL LLLuaFloater::postBuild() +BOOL LLLuaFloater::postBuild() { for (LLView *view : *getChildList()) { @@ -217,10 +217,10 @@ void LLLuaFloater::registerCallback(const std::string &ctrl_name, const std::str auto mouse_event_cb = [this, data](LLUICtrl *ctrl, const LLSD ¶m) { post(data); }; - auto mouse_event_coords_cb = [this, data](LLUICtrl *ctrl, S32 x, S32 y, MASK mask) - { + auto mouse_event_coords_cb = [this, data](LLUICtrl *ctrl, S32 x, S32 y, MASK mask) + { LLSD event(data); - post(event.with("x", x).with("y", y)); + post(event.with("x", x).with("y", y)); }; auto post_with_value = [this, data](LLSD value) @@ -260,12 +260,12 @@ void LLLuaFloater::registerCallback(const std::string &ctrl_name, const std::str { list->setDoubleClickCallback( [post_with_value, list](){ post_with_value(LLSD(list->getCurrentID())); }); } - else + else { ctrl->setDoubleClickCallback(mouse_event_coords_cb); } } - else if (event_is(event, "keystroke")) + else if (event_is(event, "keystroke")) { LLTextEditor* text_editor = dynamic_cast(ctrl); if (text_editor) @@ -278,7 +278,7 @@ void LLLuaFloater::registerCallback(const std::string &ctrl_name, const std::str line_editor->setKeystrokeCallback([post_with_value](LLLineEditor *editor, void* userdata) { post_with_value(editor->getValue()); }, NULL); } } - else + else { LL_WARNS("LuaFloater") << "Can't register callback for unknown event: " << event << " , control: " << ctrl_name << LL_ENDL; } @@ -292,7 +292,7 @@ void LLLuaFloater::post(const LLSD &data) LLEventPumps::instance().obtain(mReplyPumpName).post(stamped_data); } -void LLLuaFloater::postEvent(LLSD data, const std::string &event_name) +void LLLuaFloater::postEvent(LLSD data, const std::string &event_name) { llassert(EVENT_LIST.find(event_name) != EVENT_LIST.end()); post(data.with("event", event_name)); @@ -302,12 +302,12 @@ void LLLuaFloater::showLuaFloater(const LLSD &data) { fsyspath fs_path(data["xml_path"].asString()); std::string path = fs_path.lexically_normal().u8string(); - if (!fs_path.is_absolute()) + if (!fs_path.is_absolute()) { std::string lib_path = gDirUtilp->getExpandedFilename(LL_PATH_SCRIPTS, "lua"); path = (fsyspath(lib_path) / path).u8string(); } - + LLLuaFloater *floater = new LLLuaFloater(data); floater->buildFromFile(path); floater->openFloater(floater->getKey()); diff --git a/indra/llui/llluafloater.h b/indra/llui/llluafloater.h index ccc3ccb39b..d0641d41b0 100644 --- a/indra/llui/llluafloater.h +++ b/indra/llui/llluafloater.h @@ -1,24 +1,24 @@ -/** +/** * @file llluafloater.h * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2024, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index b6b83c4716..65211bf122 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -953,7 +953,7 @@ public: typedef LLUICtrl::CommitCallbackInfo cb_info; static void addCommit(view_listener_t *listener, const std::string &name, cb_info::EUntrustedCall handle_untrusted = cb_info::UNTRUSTED_ALLOW) { - LLUICtrl::CommitCallbackRegistry::currentRegistrar().add(name, + LLUICtrl::CommitCallbackRegistry::currentRegistrar().add(name, cb_info([listener](LLUICtrl*, const LLSD& param){ return listener->handleEvent(param); }, handle_untrusted)); } diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index d6631a9cd9..f1dc3d7c08 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -284,7 +284,7 @@ LLUICtrl::commit_signal_t::slot_type LLUICtrl::initCommitCallback(const CommitCa std::string function_name = cb.function_name; setFunctionName(function_name); LLUICtrl::CommitCallbackInfo *info = (CommitCallbackRegistry::getValue(function_name)); - if (info && info->callback_func) + if (info && info->callback_func) { if (cb.parameter.isProvided()) return boost::bind((info->callback_func), _1, cb.parameter); diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 72c20d377b..632366e801 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -2957,7 +2957,7 @@ bool LLAppearanceMgr::wearOutfit(const LLUUID &cat_id, std::string &error_msg, b return wearOutfit(stringize(cat_id), cat, error_msg, false, append); } -bool LLAppearanceMgr::wearOutfit(const std::string &desc, LLInventoryCategory* cat, +bool LLAppearanceMgr::wearOutfit(const std::string &desc, LLInventoryCategory* cat, std::string &error_msg, bool copy_items, bool append) { if (!cat) diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index d15394df15..9e624f593f 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -264,7 +264,7 @@ private: static void onOutfitRename(const LLSD& notification, const LLSD& response); // used by both wearOutfit(LLUUID) and wearOutfitByName(std::string) - bool wearOutfit(const std::string &desc, LLInventoryCategory* cat, + bool wearOutfit(const std::string &desc, LLInventoryCategory* cat, std::string &error_msg, bool copy_items, bool append); bool mAttachmentInvLinkEnabled; diff --git a/indra/newview/llfloatergodtools.cpp b/indra/newview/llfloatergodtools.cpp index 4217b3ad49..f2884c3a6e 100644 --- a/indra/newview/llfloatergodtools.cpp +++ b/indra/newview/llfloatergodtools.cpp @@ -854,7 +854,7 @@ void LLPanelRegionTools::onSelectRegion() LLPanelGridTools::LLPanelGridTools() : LLPanel() { - mCommitCallbackRegistrar.add("GridTools.FlushMapVisibilityCaches", + mCommitCallbackRegistrar.add("GridTools.FlushMapVisibilityCaches", { boost::bind(&LLPanelGridTools::onClickFlushMapVisibilityCaches, this), cb_info::UNTRUSTED_BLOCK }); } diff --git a/indra/newview/llfloaterluadebug.h b/indra/newview/llfloaterluadebug.h index e513d9a095..9f044df9d9 100644 --- a/indra/newview/llfloaterluadebug.h +++ b/indra/newview/llfloaterluadebug.h @@ -1,24 +1,24 @@ -/** +/** * @file llfloaterluadebug.h * * $LicenseInfo:firstyear=2023&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2023, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -41,7 +41,7 @@ extern "C" class LLLineEditor; class LLTextEditor; -class LLFloaterLUADebug : +class LLFloaterLUADebug : public LLFloater { public: diff --git a/indra/newview/llfloaterluascripts.cpp b/indra/newview/llfloaterluascripts.cpp index 189ee0c9d1..60cdadfbee 100644 --- a/indra/newview/llfloaterluascripts.cpp +++ b/indra/newview/llfloaterluascripts.cpp @@ -1,24 +1,24 @@ -/** +/** * @file llfloaterluascriptsinfo.cpp * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2024, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -41,16 +41,16 @@ LLFloaterLUAScripts::LLFloaterLUAScripts(const LLSD &key) mUpdateTimer(new LLTimer()), mContextMenuHandle() { - mCommitCallbackRegistrar.add("Script.OpenFolder", {[this](LLUICtrl*, const LLSD &userdata) - { + mCommitCallbackRegistrar.add("Script.OpenFolder", {[this](LLUICtrl*, const LLSD &userdata) + { if (mScriptList->hasSelectedItem()) { std::string target_folder_path = std::filesystem::path((mScriptList->getFirstSelected()->getColumn(1)->getValue().asString())).parent_path().string(); gViewerWindow->getWindow()->openFolder(target_folder_path); } }, cb_info::UNTRUSTED_BLOCK }); - mCommitCallbackRegistrar.add("Script.Terminate", {[this](LLUICtrl*, const LLSD &userdata) - { + mCommitCallbackRegistrar.add("Script.Terminate", {[this](LLUICtrl*, const LLSD &userdata) + { if (mScriptList->hasSelectedItem()) { std::string coro_name = mScriptList->getSelectedValue(); @@ -75,7 +75,7 @@ BOOL LLFloaterLUAScripts::postBuild() return TRUE; } -LLFloaterLUAScripts::~LLFloaterLUAScripts() +LLFloaterLUAScripts::~LLFloaterLUAScripts() { auto menu = mContextMenuHandle.get(); if (menu) @@ -94,7 +94,7 @@ void LLFloaterLUAScripts::draw() LLFloater::draw(); } -void LLFloaterLUAScripts::populateScriptList() +void LLFloaterLUAScripts::populateScriptList() { S32 prev_pos = mScriptList->getScrollPos(); LLSD prev_selected = mScriptList->getSelectedValue(); diff --git a/indra/newview/llfloaterluascripts.h b/indra/newview/llfloaterluascripts.h index 932c5c78dd..24d3f00a80 100644 --- a/indra/newview/llfloaterluascripts.h +++ b/indra/newview/llfloaterluascripts.h @@ -1,24 +1,24 @@ -/** +/** * @file llfloaterluascriptsinfo.h * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2024, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -30,7 +30,7 @@ class LLScrollListCtrl; -class LLFloaterLUAScripts : +class LLFloaterLUAScripts : public LLFloater { public: diff --git a/indra/newview/llinventorygallerymenu.cpp b/indra/newview/llinventorygallerymenu.cpp index 2845c5b614..133ef51a21 100644 --- a/indra/newview/llinventorygallerymenu.cpp +++ b/indra/newview/llinventorygallerymenu.cpp @@ -92,9 +92,9 @@ LLContextMenu* LLInventoryGalleryContextMenu::createMenu() registrar.add("Inventory.DoToSelected", boost::bind(&LLInventoryGalleryContextMenu::doToSelected, this, _2), LLUICtrl::cb_info::UNTRUSTED_BLOCK); registrar.add("Inventory.FileUploadLocation", boost::bind(&LLInventoryGalleryContextMenu::fileUploadLocation, this, _2), LLUICtrl::cb_info::UNTRUSTED_BLOCK); - registrar.add("Inventory.EmptyTrash", + registrar.add("Inventory.EmptyTrash", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH), LLUICtrl::cb_info::UNTRUSTED_BLOCK); - registrar.add("Inventory.EmptyLostAndFound", + registrar.add("Inventory.EmptyLostAndFound", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND), LLUICtrl::cb_info::UNTRUSTED_BLOCK); registrar.add("Inventory.DoCreate", [this](LLUICtrl*, const LLSD& data) { diff --git a/indra/newview/llinventorylistener.cpp b/indra/newview/llinventorylistener.cpp index 157e04dce3..9263663997 100644 --- a/indra/newview/llinventorylistener.cpp +++ b/indra/newview/llinventorylistener.cpp @@ -154,7 +154,7 @@ void LLInventoryListener::getBasicFolderID(LLSD const &data) void LLInventoryListener::getDirectDescendents(LLSD const &data) { Response response(LLSD(), data); - LLInventoryModel::cat_array_t* cats; + LLInventoryModel::cat_array_t* cats; LLInventoryModel::item_array_t* items; gInventory.getDirectDescendentsOf(data["folder_id"], cats, items); diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 22b51d7b72..41b1cd55b7 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -1,25 +1,25 @@ -/** +/** * @file llluamanager.cpp - * @brief classes and functions for interfacing with LUA. + * @brief classes and functions for interfacing with LUA. * * $LicenseInfo:firstyear=2023&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2023, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ * @@ -193,7 +193,7 @@ void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn r llifstream in_file; in_file.open(filename.c_str()); - if (in_file.is_open()) + if (in_file.is_open()) { // A script_finished_fn is used to initialize the LuaState. // It will be called when the LuaState is destroyed. diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h index 50f922a80f..08997c29a5 100644 --- a/indra/newview/llluamanager.h +++ b/indra/newview/llluamanager.h @@ -1,25 +1,25 @@ -/** +/** * @file llluamanager.h - * @brief classes and functions for interfacing with LUA. + * @brief classes and functions for interfacing with LUA. * * $LicenseInfo:firstyear=2023&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2023, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -110,9 +110,9 @@ class LLRequireResolver class ScriptObserver { public: - ScriptObserver(const std::string &coro_name, const std::string &filename) : mCoroName(coro_name) + ScriptObserver(const std::string &coro_name, const std::string &filename) : mCoroName(coro_name) { - LLLUAmanager::sScriptNames[mCoroName] = filename; + LLLUAmanager::sScriptNames[mCoroName] = filename; } ~ScriptObserver() { LLLUAmanager::sScriptNames.erase(mCoroName); } diff --git a/indra/newview/llpanelmaininventory.cpp b/indra/newview/llpanelmaininventory.cpp index fc5390e0dc..622698c5fa 100644 --- a/indra/newview/llpanelmaininventory.cpp +++ b/indra/newview/llpanelmaininventory.cpp @@ -127,7 +127,7 @@ LLPanelMainInventory::LLPanelMainInventory(const LLPanel::Params& p) // Menu Callbacks (non contex menus) mCommitCallbackRegistrar.add("Inventory.DoToSelected", { boost::bind(&LLPanelMainInventory::doToSelected, this, _2), LLUICtrl::cb_info::UNTRUSTED_BLOCK }); mCommitCallbackRegistrar.add("Inventory.CloseAllFolders", { boost::bind(&LLPanelMainInventory::closeAllFolders, this) }); - mCommitCallbackRegistrar.add("Inventory.EmptyTrash", { boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, + mCommitCallbackRegistrar.add("Inventory.EmptyTrash", { boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH), LLUICtrl::cb_info::UNTRUSTED_BLOCK }); mCommitCallbackRegistrar.add("Inventory.EmptyLostAndFound", { boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND), LLUICtrl::cb_info::UNTRUSTED_BLOCK }); diff --git a/indra/newview/llpanelobjectinventory.cpp b/indra/newview/llpanelobjectinventory.cpp index 64a9518e21..395e9eca82 100644 --- a/indra/newview/llpanelobjectinventory.cpp +++ b/indra/newview/llpanelobjectinventory.cpp @@ -1347,9 +1347,9 @@ LLPanelObjectInventory::LLPanelObjectInventory(const LLPanelObjectInventory::Par { // Setup context menu callbacks mCommitCallbackRegistrar.add("Inventory.DoToSelected", { boost::bind(&LLPanelObjectInventory::doToSelected, this, _2), cb_info::UNTRUSTED_BLOCK }); - mCommitCallbackRegistrar.add("Inventory.EmptyTrash", + mCommitCallbackRegistrar.add("Inventory.EmptyTrash", { boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH), cb_info::UNTRUSTED_BLOCK }); - mCommitCallbackRegistrar.add("Inventory.EmptyLostAndFound", + mCommitCallbackRegistrar.add("Inventory.EmptyLostAndFound", { boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND), cb_info::UNTRUSTED_BLOCK }); mCommitCallbackRegistrar.add("Inventory.DoCreate", { boost::bind(&do_nothing) }); mCommitCallbackRegistrar.add("Inventory.AttachObject", { boost::bind(&do_nothing) }); diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp index 0280836075..6f567e67f8 100644 --- a/indra/newview/lluilistener.cpp +++ b/indra/newview/lluilistener.cpp @@ -3,25 +3,25 @@ * @author Nat Goodspeed * @date 2009-08-18 * @brief Implementation for lluilistener. - * + * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -93,7 +93,7 @@ LLUIListener::LLUIListener(): "to the [\"parent_menu\"] within the Top menu.", &LLUIListener::addMenuItem, required_args.with("func", LLSD())); - + add("addMenuSeparator", "Add menu separator to the [\"parent_menu\"] within the Top menu.", &LLUIListener::addMenuSeparator, @@ -147,7 +147,7 @@ void LLUIListener::call(const LLSD& event) { return response.error(stringize("Function ", std::quoted(event["function"].asString()), " was not found")); } - if (info->handle_untrusted == cb_info::UNTRUSTED_BLOCK) + if (info->handle_untrusted == cb_info::UNTRUSTED_BLOCK) { return response.error(stringize("Function ", std::quoted(event["function"].asString()), " may not be called from the script")); } @@ -226,7 +226,7 @@ void LLUIListener::getValue(const LLSD& event) const const LLView* view = LLUI::getInstance()->resolvePath(root, event["path"].asString()); const LLUICtrl* ctrl(dynamic_cast(view)); - if (ctrl) + if (ctrl) { response["value"] = ctrl->getValue(); } diff --git a/indra/newview/lluilistener.h b/indra/newview/lluilistener.h index c839c03a57..8fc8e6ebb4 100644 --- a/indra/newview/lluilistener.h +++ b/indra/newview/lluilistener.h @@ -3,25 +3,25 @@ * @author Nat Goodspeed * @date 2009-08-18 * @brief Engage named functions as specified by XUI - * + * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ diff --git a/indra/newview/llviewerchat.cpp b/indra/newview/llviewerchat.cpp index 00520f100e..440c0a2f69 100644 --- a/indra/newview/llviewerchat.cpp +++ b/indra/newview/llviewerchat.cpp @@ -227,7 +227,7 @@ void LLViewerChat::formatChatMsg(const LLChat& chat, std::string& formated_msg) formated_msg = tmpmsg; } - if (chat.mIsScript) + if (chat.mIsScript) { formated_msg = LLTrans::getString("ScriptStr") + formated_msg; } diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index ae6500da70..7f8f3961d3 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -11784,7 +11784,7 @@ class LLVOAvatarListener : public LLEventAPI } void isInAir(const LLSD &request) const { - Response response(llsd::map("value", gAgentAvatarp->mInAir, + Response response(llsd::map("value", gAgentAvatarp->mInAir, "duration", gAgentAvatarp->mInAir ? gAgentAvatarp->mTimeInAir.getElapsedTimeF32() : 0), request); } }; diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index 3209d93d39..4b143b52db 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2023-09-28 * @brief Test for llluamanager. - * + * * $LicenseInfo:firstyear=2023&license=viewerlgpl$ * Copyright (c) 2023, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/test/debug.h b/indra/test/debug.h index 2f6a114761..ea9c634cc7 100644 --- a/indra/test/debug.h +++ b/indra/test/debug.h @@ -3,25 +3,25 @@ * @author Nat Goodspeed * @date 2009-05-28 * @brief Debug output for unit test code - * + * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ diff --git a/indra/test/print.h b/indra/test/print.h index 749603907e..6906eae581 100644 --- a/indra/test/print.h +++ b/indra/test/print.h @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2020-01-02 * @brief print() function for debugging - * + * * $LicenseInfo:firstyear=2020&license=viewerlgpl$ * Copyright (c) 2020, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/test/writestr.h b/indra/test/writestr.h index df1dab2f10..af8be5a3aa 100755 --- a/indra/test/writestr.h +++ b/indra/test/writestr.h @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2024-05-21 * @brief writestr() function for when iostream isn't set up - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ -- cgit v1.2.3 From a098a3d42bf862e0e3789e21f6b8f3f0e71d60d0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 29 Aug 2024 08:27:43 -0400 Subject: Add Lua script name to log messages. --- indra/llcommon/lua_function.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index f61cf3fe10..be39a7d095 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -26,6 +26,7 @@ // other Linden headers #include "fsyspath.h" #include "hexdump.h" +#include "llcoros.h" #include "lleventcoro.h" #include "llsd.h" #include "llsdutil.h" @@ -530,7 +531,8 @@ LuaState::~LuaState() // That's important because we walk the atexit table backwards, to // destroy last the things we created (passed to LL.atexit()) first. int len(lua_objlen(mState, -1)); - LL_DEBUGS("Lua") << "Registry.atexit is a table with " << len << " entries" << LL_ENDL; + LL_DEBUGS("Lua") << LLCoros::getName() << ": Registry.atexit is a table with " + << len << " entries" << LL_ENDL; // Push debug.traceback() onto the stack as lua_pcall()'s error // handler function. On error, lua_pcall() calls the specified error @@ -555,15 +557,17 @@ LuaState::~LuaState() // Use lua_pcall() because errors in any one atexit() function // shouldn't cancel the rest of them. Pass debug.traceback() as // the error handler function. - LL_DEBUGS("Lua") << "Calling atexit(" << i << ")" << LL_ENDL; + LL_DEBUGS("Lua") << LLCoros::getName() + << ": calling atexit(" << i << ")" << LL_ENDL; if (lua_pcall(mState, 0, 0, -2) != LUA_OK) { auto error{ lua_tostdstring(mState, -1) }; - LL_WARNS("Lua") << "atexit(" << i << ") error: " << error << LL_ENDL; + LL_WARNS("Lua") << LLCoros::getName() + << ": atexit(" << i << ") error: " << error << LL_ENDL; // pop error message lua_pop(mState, 1); } - LL_DEBUGS("Lua") << "atexit(" << i << ") done" << LL_ENDL; + LL_DEBUGS("Lua") << LLCoros::getName() << ": atexit(" << i << ") done" << LL_ENDL; // lua_pcall() has already popped atexit[i]: // stack contains atexit, debug.traceback() } @@ -1277,7 +1281,8 @@ setdtor_refs::~setdtor_refs() // garbage-collected, the call is completely unpredictable from // the consuming script's point of view. But what to do about this // error?? For now, just log it. - LL_WARNS("Lua") << "setdtor(" << std::quoted(desc) << ") error: " + LL_WARNS("Lua") << LLCoros::getName() + << ": setdtor(" << std::quoted(desc) << ") error: " << lua_tostring(L, -1) << LL_ENDL; lua_pop(L, 1); } @@ -1377,7 +1382,8 @@ LuaStackDelta::~LuaStackDelta() // instance to keep its contract wrt the Lua data stack. if (std::uncaught_exceptions() == 0 && mDepth + mDelta != depth) { - LL_ERRS("Lua") << mWhere << ": Lua stack went from " << mDepth << " to " << depth; + LL_ERRS("Lua") << LLCoros::getName() << ": " << mWhere + << ": Lua stack went from " << mDepth << " to " << depth; if (mDelta) { LL_CONT << ", rather than expected " << (mDepth + mDelta) << " (" << mDelta << ")"; -- cgit v1.2.3 From 11e6c77129ae27756df627ccc1ea0ffa279976e6 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Thu, 29 Aug 2024 20:04:34 +0300 Subject: Add simple metrics of Lua usage --- indra/newview/llappviewer.cpp | 2 +- indra/newview/llfloaterluadebug.cpp | 2 +- indra/newview/llluamanager.cpp | 16 ++++++++++++---- indra/newview/llluamanager.h | 5 ++++- indra/newview/llviewerstats.cpp | 5 +++++ 5 files changed, 23 insertions(+), 7 deletions(-) (limited to 'indra') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index c259275d8f..686b97390a 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1252,7 +1252,7 @@ bool LLAppViewer::init() while (scripts.next(script)) { LL_DEBUGS("InitInfo") << "LuaAutorunPath: " << absdir << ": " << script << LL_ENDL; - LLLUAmanager::runScriptFile((abspath / script).string()); + LLLUAmanager::runScriptFile((abspath / script).string(), true); } }); diff --git a/indra/newview/llfloaterluadebug.cpp b/indra/newview/llfloaterluadebug.cpp index ef24481464..1831edb452 100644 --- a/indra/newview/llfloaterluadebug.cpp +++ b/indra/newview/llfloaterluadebug.cpp @@ -127,7 +127,7 @@ void LLFloaterLUADebug::runSelectedScript(const std::vector &filena if (!filepath.empty()) { mScriptPath->setText(filepath); - LLLUAmanager::runScriptFile(filepath, [this](int count, const LLSD &result) + LLLUAmanager::runScriptFile(filepath, false, [this](int count, const LLSD &result) { completion(count, result); }); diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 22b51d7b72..befd231e92 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -49,6 +49,8 @@ #include #include +S32 LLLUAmanager::sAutorunScriptCount = 0; +S32 LLLUAmanager::sScriptCount = 0; std::map LLLUAmanager::sScriptNames; lua_function(sleep, "sleep(seconds): pause the running coroutine") @@ -172,7 +174,7 @@ LLLUAmanager::startScriptFile(const std::string& filename) // Despite returning from startScriptFile(), we need this Promise to // remain alive until the callback has fired. auto promise{ std::make_shared>() }; - runScriptFile(filename, + runScriptFile(filename, false, [promise](int count, LLSD result) { promise->set_value({ count, result }); }); return LLCoros::getFuture(*promise); @@ -183,11 +185,11 @@ LLLUAmanager::script_result LLLUAmanager::waitScriptFile(const std::string& file return startScriptFile(filename).get(); } -void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn result_cb, - script_finished_fn finished_cb) +void LLLUAmanager::runScriptFile(const std::string &filename, bool autorun, + script_result_fn result_cb, script_finished_fn finished_cb) { // A script_result_fn will be called when LuaState::expr() completes. - LLCoros::instance().launch(filename, [filename, result_cb, finished_cb]() + LLCoros::instance().launch(filename, [filename, autorun, result_cb, finished_cb]() { ScriptObserver observer(LLCoros::getName(), filename); llifstream in_file; @@ -195,6 +197,12 @@ void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn r if (in_file.is_open()) { + if (autorun) + { + sAutorunScriptCount++; + } + sScriptCount++; + // A script_finished_fn is used to initialize the LuaState. // It will be called when the LuaState is destroyed. LuaState L(finished_cb); diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h index 50f922a80f..309cb87cac 100644 --- a/indra/newview/llluamanager.h +++ b/indra/newview/llluamanager.h @@ -58,7 +58,7 @@ public: // same semantics as script_result_fn parameters typedef std::pair script_result; - static void runScriptFile(const std::string &filename, script_result_fn result_cb = {}, + static void runScriptFile(const std::string &filename, bool autorun = false, script_result_fn result_cb = {}, script_finished_fn finished_cb = {}); // Start running a Lua script file, returning an LLCoros::Future whose // get() method will pause the calling coroutine until it can deliver the @@ -84,6 +84,9 @@ public: static const std::map getScriptNames() { return sScriptNames; } + static S32 sAutorunScriptCount; + static S32 sScriptCount; + private: static std::map sScriptNames; }; diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index 62b4c390d0..60e8cf2e52 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -66,6 +66,7 @@ #include "llinventorymodel.h" #include "lluiusage.h" #include "lltranslate.h" +#include "llluamanager.h" // "Minimal Vulkan" to get max API Version @@ -673,6 +674,10 @@ void send_viewer_stats(bool include_preferences) system["shader_level"] = shader_level; + LLSD &scripts = body["scripts"]; + scripts["lua_scripts"] = LLLUAmanager::sScriptCount; + scripts["lua_auto_scripts"] = LLLUAmanager::sAutorunScriptCount; + LLSD &download = body["downloads"]; download["world_kbytes"] = F64Kilobytes(gTotalWorldData).value(); -- cgit v1.2.3 From 2d5cf36be6e0e367efec2bfa01378146269f33db Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 29 Aug 2024 21:36:39 -0400 Subject: Support next(), pairs(), ipairs() for LL.setdtor() table proxies. Replace the global next(), pairs() and ipairs() functions with a C++ function that drills down through layers of setdtor() proxy objects and then forwards the updated arguments to the original global function. Add a Luau __iter() metamethod to setdtor() proxy objects that, like other proxy metamethods, drills down to the underlying _target object. __iter() recognizes the case of a _target table which itself has a __iter() metamethod. Also add __idiv() metamethod to support integer division. Add tests for proxy // division, next(proxy), next(proxy, key), pairs(proxy), ipairs(proxy) and 'for k, v in proxy'. Also test the case where the table wrapped in the proxy has an __iter() metamethod of its own. --- indra/llcommon/lua_function.cpp | 62 ++++++++++++++++++++++++++++++ indra/newview/scripts/lua/test_setdtor.lua | 29 ++++++++++++++ 2 files changed, 91 insertions(+) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index be39a7d095..850dff1a33 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -496,6 +496,9 @@ namespace using LuaStateMap = std::unordered_map; static LuaStateMap sLuaStateMap; +// replacement next(), pairs(), ipairs() that understand setdtor() proxy args +int lua_proxydrill(lua_State* L); + } // anonymous namespace LuaState::LuaState(script_finished_fn cb): @@ -513,6 +516,28 @@ LuaState::LuaState(script_finished_fn cb): lua_register(mState, "print", LuaFunction::get("print_info")); // We don't want to have to prefix require(). lua_register(mState, "require", LuaFunction::get("require")); + + // Replace certain key global functions so they understand our + // LL.setdtor() proxy objects. + // (We could also do this for selected library functions as well, + // e.g. the table, string, math libraries... let's see if needed.) + for (const auto& func : { "next", "pairs", "ipairs" }) + { + // push the function's name string twice + lua_pushstring(mState, func); + lua_pushvalue(mState, -1); + // stack: name, name + // look up the existing global + lua_rawget(mState, LUA_GLOBALSINDEX); + // stack: name, global function + // bind original function as the upvalue for lua_proxydrill() + lua_pushcclosure(mState, lua_proxydrill, + stringize("lua_proxydrill(", func, ')').c_str(), + 1); + // stack: name, lua_proxydrill(func) + // global name = lua_proxydrill(func) + lua_rawset(mState, LUA_GLOBALSINDEX); + } } LuaState::~LuaState() @@ -1165,6 +1190,14 @@ void setdtor_refs::push_metatable(lua_State* L) -- don't fret about arg._target's __tostring metamethod, -- if any, because built-in tostring() deals with that return tostring(arg._target) + end, + __iter = function(arg) + local iter = (getmetatable(arg._target) or {}).__iter + if iter then + return iter(arg._target) + else + return next, arg._target + end end } )-", @@ -1172,6 +1205,7 @@ void setdtor_refs::push_metatable(lua_State* L) binop("sub", "-"), binop("mul", "*"), binop("div", "/"), + binop("idiv", "//"), binop("mod", "%"), binop("pow", "^"), binop("concat", ".."), @@ -1254,6 +1288,34 @@ int setdtor_refs::meta__index(lua_State* L) return 1; } +// replacement for global next(), pairs(), ipairs(): +// its lua_upvalueindex(1) is the original function it's replacing +int lua_proxydrill(lua_State* L) +{ + // Accept however many arguments the original function normally accepts. + // If our first arg is a userdata, check if it's a setdtor_refs proxy. + // Drill through as many levels of proxy wrapper as needed. + while (const setdtor_refs* ptr = lua_toclass(L, 1)) + { + // push original object + lua_getref(L, ptr->objref); + // replace first argument with that + lua_replace(L, 1); + } + // We've reached a first argument that's not a setdtor() proxy. + // How many arguments were we passed, anyway? + int args = lua_gettop(L); + // Push the original function, captured as our upvalue. + lua_pushvalue(L, lua_upvalueindex(1)); + // Shift the stack so the original function is first. + lua_insert(L, 1); + // Call the original function with all original args, no error checking. + // Don't truncate however many values that function returns. + lua_call(L, args, LUA_MULTRET); + // Return as many values as the original function returned. + return lua_gettop(L); +} + // When Lua destroys a setdtor_refs userdata object, either from garbage // collection or from LL.atexit(lua_destroybounduserdata), it's time to keep // its promise to call the specified Lua destructor function with the diff --git a/indra/newview/scripts/lua/test_setdtor.lua b/indra/newview/scripts/lua/test_setdtor.lua index 61ed86dcc8..ec5cd47e93 100644 --- a/indra/newview/scripts/lua/test_setdtor.lua +++ b/indra/newview/scripts/lua/test_setdtor.lua @@ -30,6 +30,8 @@ print(`n * {i} =`, n * i) print(`{i} * n =`, i * n) print(`n / {i} =`, n / i) print(`{i} / n =`, i / n) +print(`n // {i} =`, n // i) +print(`{i} // n =`, i // n) print(`n % {i} =`, n % i) print(`{i} % n =`, i % n) print(`n ^ {i} =`, n ^ i) @@ -48,10 +50,37 @@ t = LL.setdtor('table', {'[1]', '[2]', abc='.abc', def='.def'}, print('t =', inspect(t)) print('t._target =', inspect(t._target)) print('#t =', #t) +print('next(t) =', next(t)) +print('next(t, 1) =', next(t, 1)) print('t[2] =', t[2]) print('t.def =', t.def) t[1] = 'new [1]' print('t[1] =', t[1]) +print('for k, v in pairs(t) do') +for k, v in pairs(t) do + print(`{k}: {v}`) +end +print('for k, v in ipairs(t) do') +for k, v in ipairs(t) do + print(`{k}: {v}`) +end +print('for k, v in t do') +for k, v in t do + print(`{k}: {v}`) +end +-- and now for something completely different +setmetatable( + t._target, + { + __iter = function(arg) + return next, {'alternate', '__iter'} + end + } +) +print('for k, v in t with __iter() metamethod do') +for k, v in t do + print(`{k}: {v}`) +end print('function') f = LL.setdtor('function', function(a, b) return (a .. b) end, print) -- cgit v1.2.3 From 6e47dc1af90c242c792c90e93d013355c9a43b97 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 30 Aug 2024 12:18:20 +0300 Subject: Add Lua api to start/stop playing animation --- indra/newview/llagentlistener.cpp | 66 +++++++++++++++++++++++++++ indra/newview/llagentlistener.h | 4 ++ indra/newview/scripts/lua/require/LLAgent.lua | 14 ++++++ 3 files changed, 84 insertions(+) (limited to 'indra') diff --git a/indra/newview/llagentlistener.cpp b/indra/newview/llagentlistener.cpp index 80460666a5..95b7ebd5db 100644 --- a/indra/newview/llagentlistener.cpp +++ b/indra/newview/llagentlistener.cpp @@ -34,12 +34,14 @@ #include "llagentcamera.h" #include "llvoavatar.h" #include "llcommandhandler.h" +#include "llinventorymodel.h" #include "llslurl.h" #include "llurldispatcher.h" #include "llviewernetwork.h" #include "llviewerobject.h" #include "llviewerobjectlist.h" #include "llviewerregion.h" +#include "llvoavatarself.h" #include "llsdutil.h" #include "llsdutil_math.h" #include "lltoolgrab.h" @@ -157,6 +159,19 @@ LLAgentListener::LLAgentListener(LLAgent &agent) add("removeCameraParams", "Reset Follow camera params", &LLAgentListener::removeFollowCamParams); + + add("playAnimation", + "Play [\"item_id\"] animation locally (by default) or [\"inworld\"] (when set to true)", + &LLAgentListener::playAnimation, + llsd::map("item_id", LLSD(), "reply", LLSD())); + add("stopAnimation", + "Stop playing [\"item_id\"] animation", + &LLAgentListener::stopAnimation, + llsd::map("item_id", LLSD(), "reply", LLSD())); + add("getAnimationInfo", + "Return information about [\"item_id\"] animation", + &LLAgentListener::getAnimationInfo, + llsd::map("item_id", LLSD(), "reply", LLSD())); } void LLAgentListener::requestTeleport(LLSD const & event_data) const @@ -591,3 +606,54 @@ void LLAgentListener::removeFollowCamParams(LLSD const & event) const { LLFollowCamMgr::getInstance()->removeFollowCamParams(gAgentID); } + +void LLAgentListener::playAnimation(LLSD const &event_data) +{ + Response response(LLSD(), event_data); + LLViewerInventoryItem* item = gInventory.getItem(event_data["item_id"].asUUID()); + if (!item || (item->getInventoryType() != LLInventoryType::IT_ANIMATION)) + { + return response.error(stringize("Item ", std::quoted(event_data["item_id"].asString()), " was not found")); + } + LLUUID assset_id = item->getAssetUUID(); + if(event_data["inworld"].asBoolean()) + { + gAgent.sendAnimationRequest(assset_id, ANIM_REQUEST_START); + } + else + { + gAgentAvatarp->startMotion(assset_id); + } +} + +void LLAgentListener::stopAnimation(LLSD const &event_data) +{ + Response response(LLSD(), event_data); + LLViewerInventoryItem* item = gInventory.getItem(event_data["item_id"].asUUID()); + if (!item || (item->getInventoryType() != LLInventoryType::IT_ANIMATION)) + { + return response.error(stringize("Item ", std::quoted(event_data["item_id"].asString()), " was not found")); + } + LLUUID assset_id = item->getAssetUUID(); + gAgentAvatarp->stopMotion(assset_id); + gAgent.sendAnimationRequest(assset_id, ANIM_REQUEST_STOP); +} + +void LLAgentListener::getAnimationInfo(LLSD const &event_data) +{ + Response response(LLSD(), event_data); + LLUUID item_id(event_data["item_id"].asUUID()); + LLViewerInventoryItem* item = gInventory.getItem(item_id); + if (!item || (item->getInventoryType() != LLInventoryType::IT_ANIMATION)) + { + return response.error(stringize("Item ", std::quoted(item_id.asString()), " was not found")); + } + // if motion exists, will return existing one + LLMotion* motion = gAgentAvatarp->createMotion(item->getAssetUUID()); + response["anim_info"].insert(item_id.asString(), + llsd::map("duration", motion->getDuration(), + "is_loop", motion->getLoop(), + "num_joints", motion->getNumJointMotions(), + "asset_id", item->getAssetUUID(), + "priority", motion->getPriority())); +} diff --git a/indra/newview/llagentlistener.h b/indra/newview/llagentlistener.h index 2a24de3f52..cf914a17d0 100644 --- a/indra/newview/llagentlistener.h +++ b/indra/newview/llagentlistener.h @@ -60,6 +60,10 @@ private: void setFollowCamParams(LLSD const & event_data) const; void setFollowCamActive(LLSD const & event_data) const; void removeFollowCamParams(LLSD const & event_data) const; + + void playAnimation(LLSD const &event_data); + void stopAnimation(LLSD const &event_data); + void getAnimationInfo(LLSD const &event_data); LLViewerObject * findObjectClosestTo( const LLVector3 & position ) const; diff --git a/indra/newview/scripts/lua/require/LLAgent.lua b/indra/newview/scripts/lua/require/LLAgent.lua index 5ee092f2f6..56907f53cd 100644 --- a/indra/newview/scripts/lua/require/LLAgent.lua +++ b/indra/newview/scripts/lua/require/LLAgent.lua @@ -53,4 +53,18 @@ function LLAgent.removeCamParams() leap.send('LLAgent', {op = 'removeCameraParams'}) end +function LLAgent.playAnimation(...) + local args = mapargs('item_id,inworld', ...) + args.op = 'playAnimation' + return leap.request('LLAgent', args) +end + +function LLAgent.stopAnimation(item_id) + return leap.request('LLAgent', {op = 'stopAnimation', item_id=item_id}) +end + +function LLAgent.getAnimationInfo(item_id) + return leap.request('LLAgent', {op = 'getAnimationInfo', item_id=item_id}).anim_info +end + return LLAgent -- cgit v1.2.3 From 4312a2b7703f6ba35a1cdcd1edc48dddfe084270 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 30 Aug 2024 14:48:36 +0300 Subject: Add throttle for playing an animation; add demo script --- indra/newview/llagentlistener.cpp | 59 +++++++++++++++------------ indra/newview/llagentlistener.h | 4 ++ indra/newview/scripts/lua/require/LLAgent.lua | 4 ++ indra/newview/scripts/lua/test_animation.lua | 28 +++++++++++++ 4 files changed, 70 insertions(+), 25 deletions(-) create mode 100644 indra/newview/scripts/lua/test_animation.lua (limited to 'indra') diff --git a/indra/newview/llagentlistener.cpp b/indra/newview/llagentlistener.cpp index 95b7ebd5db..4980c7075f 100644 --- a/indra/newview/llagentlistener.cpp +++ b/indra/newview/llagentlistener.cpp @@ -49,9 +49,12 @@ #include "llagentcamera.h" #include +static const F64 PLAY_ANIM_THROTTLE_PERIOD = 1.f; + LLAgentListener::LLAgentListener(LLAgent &agent) : LLEventAPI("LLAgent", "LLAgent listener to (e.g.) teleport, sit, stand, etc."), + mPlayAnimThrottle("playAnimation", &LLAgentListener::playAnimation_, this, PLAY_ANIM_THROTTLE_PERIOD), mAgent(agent) { add("requestTeleport", @@ -607,53 +610,59 @@ void LLAgentListener::removeFollowCamParams(LLSD const & event) const LLFollowCamMgr::getInstance()->removeFollowCamParams(gAgentID); } -void LLAgentListener::playAnimation(LLSD const &event_data) +LLViewerInventoryItem* get_anim_item(LLEventAPI::Response &response, const LLSD &event_data) { - Response response(LLSD(), event_data); LLViewerInventoryItem* item = gInventory.getItem(event_data["item_id"].asUUID()); if (!item || (item->getInventoryType() != LLInventoryType::IT_ANIMATION)) { - return response.error(stringize("Item ", std::quoted(event_data["item_id"].asString()), " was not found")); + response.error(stringize("Animation item ", std::quoted(event_data["item_id"].asString()), " was not found")); + return NULL; + } + return item; +} + +void LLAgentListener::playAnimation(LLSD const &event_data) +{ + Response response(LLSD(), event_data); + if (LLViewerInventoryItem* item = get_anim_item(response, event_data)) + { + mPlayAnimThrottle(item->getAssetUUID(), event_data["inworld"].asBoolean()); } - LLUUID assset_id = item->getAssetUUID(); - if(event_data["inworld"].asBoolean()) +} + +void LLAgentListener::playAnimation_(const LLUUID& asset_id, const bool inworld) +{ + if (inworld) { - gAgent.sendAnimationRequest(assset_id, ANIM_REQUEST_START); + mAgent.sendAnimationRequest(asset_id, ANIM_REQUEST_START); } else { - gAgentAvatarp->startMotion(assset_id); + gAgentAvatarp->startMotion(asset_id); } } void LLAgentListener::stopAnimation(LLSD const &event_data) { Response response(LLSD(), event_data); - LLViewerInventoryItem* item = gInventory.getItem(event_data["item_id"].asUUID()); - if (!item || (item->getInventoryType() != LLInventoryType::IT_ANIMATION)) + if (LLViewerInventoryItem* item = get_anim_item(response, event_data)) { - return response.error(stringize("Item ", std::quoted(event_data["item_id"].asString()), " was not found")); + gAgentAvatarp->stopMotion(item->getAssetUUID()); + mAgent.sendAnimationRequest(item->getAssetUUID(), ANIM_REQUEST_STOP); } - LLUUID assset_id = item->getAssetUUID(); - gAgentAvatarp->stopMotion(assset_id); - gAgent.sendAnimationRequest(assset_id, ANIM_REQUEST_STOP); } void LLAgentListener::getAnimationInfo(LLSD const &event_data) { Response response(LLSD(), event_data); - LLUUID item_id(event_data["item_id"].asUUID()); - LLViewerInventoryItem* item = gInventory.getItem(item_id); - if (!item || (item->getInventoryType() != LLInventoryType::IT_ANIMATION)) + if (LLViewerInventoryItem* item = get_anim_item(response, event_data)) { - return response.error(stringize("Item ", std::quoted(item_id.asString()), " was not found")); + // if motion exists, will return existing one + LLMotion* motion = gAgentAvatarp->createMotion(item->getAssetUUID()); + response["anim_info"] = llsd::map("duration", motion->getDuration(), + "is_loop", motion->getLoop(), + "num_joints", motion->getNumJointMotions(), + "asset_id", item->getAssetUUID(), + "priority", motion->getPriority()); } - // if motion exists, will return existing one - LLMotion* motion = gAgentAvatarp->createMotion(item->getAssetUUID()); - response["anim_info"].insert(item_id.asString(), - llsd::map("duration", motion->getDuration(), - "is_loop", motion->getLoop(), - "num_joints", motion->getNumJointMotions(), - "asset_id", item->getAssetUUID(), - "priority", motion->getPriority())); } diff --git a/indra/newview/llagentlistener.h b/indra/newview/llagentlistener.h index cf914a17d0..c77d1b3fc9 100644 --- a/indra/newview/llagentlistener.h +++ b/indra/newview/llagentlistener.h @@ -31,6 +31,7 @@ #define LL_LLAGENTLISTENER_H #include "lleventapi.h" +#include "throttle.h" class LLAgent; class LLSD; @@ -62,6 +63,7 @@ private: void removeFollowCamParams(LLSD const & event_data) const; void playAnimation(LLSD const &event_data); + void playAnimation_(const LLUUID& asset_id, const bool inworld); void stopAnimation(LLSD const &event_data); void getAnimationInfo(LLSD const &event_data); @@ -70,6 +72,8 @@ private: private: LLAgent & mAgent; LLUUID mFollowTarget; + + LogThrottle mPlayAnimThrottle; }; #endif // LL_LLAGENTLISTENER_H diff --git a/indra/newview/scripts/lua/require/LLAgent.lua b/indra/newview/scripts/lua/require/LLAgent.lua index 56907f53cd..07ef1e0b0b 100644 --- a/indra/newview/scripts/lua/require/LLAgent.lua +++ b/indra/newview/scripts/lua/require/LLAgent.lua @@ -53,6 +53,8 @@ function LLAgent.removeCamParams() leap.send('LLAgent', {op = 'removeCameraParams'}) end +-- Play specified animation by "item_id" locally +-- if "inworld" is specified as true, animation will be played inworld instead function LLAgent.playAnimation(...) local args = mapargs('item_id,inworld', ...) args.op = 'playAnimation' @@ -63,6 +65,8 @@ function LLAgent.stopAnimation(item_id) return leap.request('LLAgent', {op = 'stopAnimation', item_id=item_id}) end +-- Get animation info by "item_id" +-- reply contains "duration", "is_loop", "num_joints", "asset_id", "priority" function LLAgent.getAnimationInfo(item_id) return leap.request('LLAgent', {op = 'getAnimationInfo', item_id=item_id}).anim_info end diff --git a/indra/newview/scripts/lua/test_animation.lua b/indra/newview/scripts/lua/test_animation.lua new file mode 100644 index 0000000000..c16fef4918 --- /dev/null +++ b/indra/newview/scripts/lua/test_animation.lua @@ -0,0 +1,28 @@ +LLInventory = require 'LLInventory' +LLAgent = require 'LLAgent' + +-- Get 'Animations' folder id (you can see all folder types via LLInventory.getFolderTypeNames()) +animations_id = LLInventory.getBasicFolderID('animatn') +-- Get animations from the 'Animation' folder (you can see all folder types via LLInventory.getAssetTypeNames()) +anims = LLInventory.collectDescendentsIf{folder_id=animations_id, type="animatn"}.items + +local anim_ids = {} +for key in pairs(anims) do + table.insert(anim_ids, key) +end + +-- Start playing a random animation +math.randomseed(os.time()) +local random_id = anim_ids[math.random(#anim_ids)] +local anim_info = LLAgent.getAnimationInfo(random_id) + +print("Starting animation locally: " .. anims[random_id].name) +print("Loop: " .. anim_info.is_loop .. " Joints: " .. anim_info.num_joints .. " Duration " .. tonumber(string.format("%.2f", anim_info.duration))) +LLAgent.playAnimation{item_id=random_id} + +-- Stop animation after 3 sec if it's looped or longer than 3 sec +if anim_info.is_loop == 1 or anim_info.duration > 3 then + LL.sleep(3) + print("Stop animation.") + LLAgent.stopAnimation(random_id) +end -- cgit v1.2.3 From a662fea2840bd6e8b7856b5f3c8e2f43e706178e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 30 Aug 2024 17:07:58 -0400 Subject: Change LLInstanceTracker::destruct() to erase(). One could argue that LLInstanceTracker is a container of sorts, and erase() is more conventional. This affects no other code, as destruct() is not currently referenced. --- indra/llcommon/llinstancetracker.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index aba9f1187b..722cb483fe 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -262,19 +262,19 @@ public: virtual const KEY& getKey() const { return mInstanceKey; } /// for use ONLY for an object we're sure resides on the heap! - static bool destruct(const KEY& key) + static bool erase(const KEY& key) { - return destruct(getInstance(key)); + return erase(getInstance(key)); } /// for use ONLY for an object we're sure resides on the heap! - static bool destruct(const weak_t& ptr) + static bool erase(const weak_t& ptr) { - return destruct(ptr.lock()); + return erase(ptr.lock()); } /// for use ONLY for an object we're sure resides on the heap! - static bool destruct(const ptr_t& ptr) + static bool erase(const ptr_t& ptr) { if (! ptr) { @@ -480,13 +480,13 @@ public: using key_snapshot_of = instance_snapshot_of; /// for use ONLY for an object we're sure resides on the heap! - static bool destruct(const weak_t& ptr) + static bool erase(const weak_t& ptr) { - return destruct(ptr.lock()); + return erase(ptr.lock()); } /// for use ONLY for an object we're sure resides on the heap! - static bool destruct(const ptr_t& ptr) + static bool erase(const ptr_t& ptr) { if (! ptr) { -- cgit v1.2.3 From 2eb0d173c3d24997a70b5ca9e78562ba48fc2f51 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 30 Aug 2024 17:13:26 -0400 Subject: Add LLIntTracker, an LLInstanceTracker with generated keys. The point of LLIntTracker is to generate its keys implicitly, so that its int getKey() can be treated more or less like an instance pointer, with the added bonus that the key can be passed around via LLSD. LLIntTracker generates random int keys to try to make it a little harder for one script to mess with an LLIntTracker instance belonging to another. --- indra/llcommon/CMakeLists.txt | 1 + indra/llcommon/llinttracker.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 indra/llcommon/llinttracker.h (limited to 'indra') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 8472eac9f6..8577d94be1 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -189,6 +189,7 @@ set(llcommon_HEADER_FILES llinitparam.h llinstancetracker.h llinstancetrackersubclass.h + llinttracker.h llkeybind.h llkeythrottle.h llleap.h diff --git a/indra/llcommon/llinttracker.h b/indra/llcommon/llinttracker.h new file mode 100644 index 0000000000..86c30bc7aa --- /dev/null +++ b/indra/llcommon/llinttracker.h @@ -0,0 +1,43 @@ +/** + * @file llinttracker.h + * @author Nat Goodspeed + * @date 2024-08-30 + * @brief LLIntTracker isa LLInstanceTracker with generated int keys. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLINTTRACKER_H) +#define LL_LLINTTRACKER_H + +#include "llinstancetracker.h" + +template +class LLIntTracker: public LLInstanceTracker +{ + using super = LLInstanceTracker; +public: + LLIntTracker(): + super(getUniqueKey()) + {} + +private: + static int getUniqueKey() + { + // Find a random key that does NOT already correspond to an instance. + // Passing a duplicate key to LLInstanceTracker would do Bad Things. + int key; + do + { + key = std::rand(); + } while (super::getInstance(key)); + // This could be racy, if we were instantiating new LLIntTrackers + // on multiple threads. If we need that, have to lock between checking + // getInstance() and constructing the new super. + return key; + } +}; + +#endif /* ! defined(LL_LLINTTRACKER_H) */ -- cgit v1.2.3 From 23f0aafb74551a741de8c87d62d74e7c6cee8b01 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 31 Aug 2024 09:42:52 -0400 Subject: Give certain LLInventory queries an API based on result sets. Introduce abstract base class InvResultSet, derived from LLIntTracker so each instance has a unique int key. InvResultSet supports virtual getLength() and getSlice() operations. getSlice() returns an LLSD array limited to MAX_ITEM_LIMIT result set entries. It permits retrieving a "slice" of the contained result set starting at an arbitrary index. A sequence of getSlice() calls can eventually retrieve a whole result set. InvResultSet has subclasses CatResultSet containing cat_array_t, and ItemResultSet containing item_array_t. Each implements a virtual method that produces an LLSD map from a single array item. Make LLInventoryListener::getItemsInfo(), getDirectDescendants() and collectDescendantsIf() instantiate heap CatResultSet and ItemResultSet objects containing the resultant LLPointer arrays, and return their int keys for categories and items. Add LLInventoryListener::getSlice() and closeResult() methods that accept the int keys of result sets. getSlice() returns the requested LLSD array to its caller, while closeResult() is fire-and-forget. Because bulk data transfer is now performed by getSlice() rather than by collectDescendantsIf(), change the latter's "limit" default to unlimited. Allow the C++ code to collect an arbitrary number of LLPointer array entries, as long as getSlice() limits retrieval overhead. Spell "descendants" correctly, unlike the "descendents" spelling embedded in the rest of the viewer... sigh. Make the Lua module provide both spellings. Make MAX_ITEM_LIMIT a U32 instead of F32. In LLInventory.lua, store int result set keys from 'getItemsInfo', 'getDirectDescendants' and 'collectDescendantsIf' in a table with a close() function. The close() function invokes 'closeResult' with the bound int keys. Give that table an __index() metamethod that recognizes only 'categories' and 'items' keys: anything else returns nil. For either of the recognized keys, call 'getSlice' with the corresponding result set key to retrieve (the initial slice of) the actual result set. Cache that result. Lazy retrieval means that if the caller only cares about categories, or only about items, the other result set need never be retrieved at all. This is a first step: like the previous code, it still retrieves only up to the first 100 result set entries. But the C++ code now supports retrieval of additional slices, so extending result set retrieval is mostly Lua work. Finally, wrap the table-with-metamethod in an LL.setdtor() proxy whose destructor calls its close() method to tell LLInventoryListener to destroy the CatResultSet and ItemResultSet with the bound keys. --- indra/newview/llinventorylistener.cpp | 261 ++++++++++++++++++---- indra/newview/llinventorylistener.h | 9 +- indra/newview/scripts/lua/require/LLInventory.lua | 79 ++++++- 3 files changed, 289 insertions(+), 60 deletions(-) (limited to 'indra') diff --git a/indra/newview/llinventorylistener.cpp b/indra/newview/llinventorylistener.cpp index 157e04dce3..c330ef42a0 100644 --- a/indra/newview/llinventorylistener.cpp +++ b/indra/newview/llinventorylistener.cpp @@ -28,12 +28,13 @@ #include "llinventorylistener.h" #include "llappearancemgr.h" +#include "llinttracker.h" #include "llinventoryfunctions.h" #include "lltransutil.h" #include "llwearableitemslist.h" #include "stringize.h" -static const F32 MAX_ITEM_LIMIT = 100; +constexpr U32 MAX_ITEM_LIMIT = 100; LLInventoryListener::LLInventoryListener() : LLEventAPI("LLInventory", @@ -41,7 +42,7 @@ LLInventoryListener::LLInventoryListener() { add("getItemsInfo", "Return information about items or folders defined in [\"item_ids\"]:\n" - "reply will contain [\"items\"] and [\"categories\"] tables accordingly", + "reply will contain [\"items\"] and [\"categories\"] result set keys", &LLInventoryListener::getItemsInfo, llsd::map("item_ids", LLSD(), "reply", LLSD())); @@ -61,78 +62,190 @@ LLInventoryListener::LLInventoryListener() &LLInventoryListener::getBasicFolderID, llsd::map("ft_name", LLSD(), "reply", LLSD())); - add("getDirectDescendents", - "Return the direct descendents(both items and folders) of the [\"folder_id\"]", - &LLInventoryListener::getDirectDescendents, + add("getDirectDescendants", + "Return result set keys [\"categories\"] and [\"items\"] for the direct\n" + "descendants of the [\"folder_id\"]", + &LLInventoryListener::getDirectDescendants, llsd::map("folder_id", LLSD(), "reply", LLSD())); - add("collectDescendentsIf", - "Return the descendents(both items and folders) of the [\"folder_id\"], if it passes specified filters:\n" + add("collectDescendantsIf", + "Return result set keys [\"categories\"] and [\"items\"] for the descendants\n" + "of the [\"folder_id\"], if it passes specified filters:\n" "[\"name\"] is a substring of object's name,\n" "[\"desc\"] is a substring of object's description,\n" "asset [\"type\"] corresponds to the string name of the object's asset type\n" - "[\"limit\"] sets item count limit in reply, maximum and default is 100\n" + "[\"limit\"] sets item count limit in result set (default unlimited)\n" "[\"filter_links\"]: EXCLUDE_LINKS - don't show links, ONLY_LINKS - only show links, INCLUDE_LINKS - show links too (default)", - &LLInventoryListener::collectDescendentsIf, + &LLInventoryListener::collectDescendantsIf, llsd::map("folder_id", LLSD(), "reply", LLSD())); - } - -void add_item_info(LLEventAPI::Response& response, LLViewerInventoryItem* item) -{ - response["items"].insert(item->getUUID().asString(), - llsd::map("name", item->getName(), - "parent_id", item->getParentUUID(), - "desc", item->getDescription(), - "inv_type", LLInventoryType::lookup(item->getInventoryType()), - "asset_type", LLAssetType::lookup(item->getType()), - "creation_date", (S32) item->getCreationDate(), - "asset_id", item->getAssetUUID(), - "is_link", item->getIsLinkType(), - "linked_id", item->getLinkedUUID())); +/*==========================================================================*| + add("getSingle", + "Return LLSD [\"single\"] for a single folder or item from the specified\n" + "[\"result\"] key at the specified 0-relative [\"index\"].", + &LLInventoryListener::getSingle, + llsd::map("result", LLSD::Integer(), "index", LLSD::Integer(), + "reply", LLSD::String())); +|*==========================================================================*/ + + add("getSlice", + stringize( + "Return an LLSD array [\"slice\"] from the specified [\"result\"] key\n" + "starting at 0-relative [\"index\"] with (up to) [\"count\"] entries.\n" + "count is limited to ", MAX_ITEM_LIMIT, " (default and max)."), + &LLInventoryListener::getSlice, + llsd::map("result", LLSD::Integer(), "index", LLSD::Integer(), + "reply", LLSD::String())); + + add("closeResult", + "Release resources associated with specified [\"result\"] key,\n" + "or keys if [\"result\"] is an array.", + &LLInventoryListener::closeResult, + llsd::map("result", LLSD())); } -void add_cat_info(LLEventAPI::Response &response, LLViewerInventoryCategory *cat) +// This abstract base class defines the interface for CatResultSet and +// ItemResultSet. It isa LLIntTracker so we can pass its unique int key to a +// consuming script via LLSD. +struct InvResultSet: public LLIntTracker { - response["categories"].insert(cat->getUUID().asString(), - llsd::map("name", cat->getName(), - "parent_id", cat->getParentUUID(), - "type", LLFolderType::lookup(cat->getPreferredType()))); -} + // Get the length of the result set. Indexes are 0-relative. + virtual int getLength() const = 0; +/*==========================================================================*| + // Retrieve LLSD corresponding to a single entry from the result set, + // with index validation. + LLSD getSingle(int index) const + { + if (0 <= index && index < getLength()) + { + return getSingle_(index); + } + else + { + return {}; + } + } +|*==========================================================================*/ + // Retrieve LLSD corresponding to a single entry from the result set, + // once we're sure the index is valid. + virtual LLSD getSingle(int index) const = 0; + // Retrieve LLSD corresponding to a "slice" of the result set: a + // contiguous sub-array starting at index. The returned LLSD array might + // be shorter than count entries if count > MAX_ITEM_LIMIT, or if the + // specified slice contains the end of the result set. + LLSD getSlice(int index, int count) const + { + // only call getLength() once + auto length = getLength(); + // Adjust bounds [start, end) to overlap the actual result set from + // [0, getLength()). Permit negative index; e.g. with a result set + // containing 5 entries, getSlice(-2, 5) will adjust start to 0 and + // end to 3. + int start = llclamp(index, 0, length); + // Constrain count to MAX_ITEM_LIMIT even before clamping end. + int end = llclamp(index + llclamp(count, 0, MAX_ITEM_LIMIT), 0, length); + LLSD result{ LLSD::emptyArray() }; + // beware of count == 0, or an [index, count) range that doesn't even + // overlap [0, length) at all + if (end > start) + { + // right away expand the result array to the size we'll need + result[end - 1] = LLSD(); + for (int i = start; i < end; ++i) + { + result[i] = getSingle(i); + } + } + return result; + } + + /*---------------- the rest is solely for debug logging ----------------*/ + std::string mName; + + friend std::ostream& operator<<(std::ostream& out, const InvResultSet& self) + { + return out << "InvResultSet(" << self.mName << ", " << self.getKey() << ")"; + } + + InvResultSet(const std::string& name): + mName(name) + { + LL_DEBUGS("Lua") << *this << LL_ENDL; + } + virtual ~InvResultSet() + { + // We want to be able to observe that the consuming script uses + // LL.setdtor() to eventually destroy each of these InvResultSets. + LL_DEBUGS("Lua") << "~" << *this << LL_ENDL; + } +}; -void add_objects_info(LLEventAPI::Response& response, LLInventoryModel::cat_array_t cat_array, LLInventoryModel::item_array_t item_array) +// This struct captures (possibly large) category results from +// getDirectDescendants() and collectDescendantsIf(). +struct CatResultSet: public InvResultSet { - for (auto &p : item_array) + CatResultSet(): InvResultSet("categories") {} + LLInventoryModel::cat_array_t mCategories; + + int getLength() const override { return narrow(mCategories.size()); } + LLSD getSingle(int index) const override { - add_item_info(response, p); + auto cat = mCategories[index]; + return llsd::map("name", cat->getName(), + "parent_id", cat->getParentUUID(), + "type", LLFolderType::lookup(cat->getPreferredType())); } - for (auto &p : cat_array) +}; + +// This struct captures (possibly large) item results from +// getDirectDescendants() and collectDescendantsIf(). +struct ItemResultSet: public InvResultSet +{ + ItemResultSet(): InvResultSet("items") {} + LLInventoryModel::item_array_t mItems; + + int getLength() const override { return narrow(mItems.size()); } + LLSD getSingle(int index) const override { - add_cat_info(response, p); + auto item = mItems[index]; + return llsd::map("name", item->getName(), + "parent_id", item->getParentUUID(), + "desc", item->getDescription(), + "inv_type", LLInventoryType::lookup(item->getInventoryType()), + "asset_type", LLAssetType::lookup(item->getType()), + "creation_date", LLSD::Integer(item->getCreationDate()), + "asset_id", item->getAssetUUID(), + "is_link", item->getIsLinkType(), + "linked_id", item->getLinkedUUID()); } -} +}; void LLInventoryListener::getItemsInfo(LLSD const &data) { Response response(LLSD(), data); + auto catresult = new CatResultSet; + auto itemresult = new ItemResultSet; + uuid_vec_t ids = LLSDParam(data["item_ids"]); for (auto &it : ids) { LLViewerInventoryItem* item = gInventory.getItem(it); if (item) { - add_item_info(response, item); + itemresult->mItems.push_back(item); } else { LLViewerInventoryCategory *cat = gInventory.getCategory(it); if (cat) { - add_cat_info(response, cat); + catresult->mCategories.push_back(cat); } } } + response["categories"] = catresult->getKey(); + response["items"] = itemresult->getKey(); } void LLInventoryListener::getFolderTypeNames(LLSD const &data) @@ -151,14 +264,21 @@ void LLInventoryListener::getBasicFolderID(LLSD const &data) } -void LLInventoryListener::getDirectDescendents(LLSD const &data) +void LLInventoryListener::getDirectDescendants(LLSD const &data) { Response response(LLSD(), data); LLInventoryModel::cat_array_t* cats; LLInventoryModel::item_array_t* items; gInventory.getDirectDescendentsOf(data["folder_id"], cats, items); - add_objects_info(response, *cats, *items); + auto catresult = new CatResultSet; + auto itemresult = new ItemResultSet; + + catresult->mCategories = *cats; + itemresult->mItems = *items; + + response["categories"] = catresult->getKey(); + response["items"] = itemresult->getKey(); } struct LLFilteredCollector : public LLInventoryCollectFunctor @@ -173,7 +293,11 @@ struct LLFilteredCollector : public LLInventoryCollectFunctor LLFilteredCollector(LLSD const &data); virtual ~LLFilteredCollector() {} virtual bool operator()(LLInventoryCategory *cat, LLInventoryItem *item) override; - virtual bool exceedsLimit() override { return (mItemLimit <= mItemCount); }; + virtual bool exceedsLimit() override + { + // mItemLimit == 0 means unlimited + return (mItemLimit && mItemLimit <= mItemCount); + } protected: bool checkagainstType(LLInventoryCategory *cat, LLInventoryItem *item); @@ -189,7 +313,7 @@ struct LLFilteredCollector : public LLInventoryCollectFunctor S32 mItemCount; }; -void LLInventoryListener::collectDescendentsIf(LLSD const &data) +void LLInventoryListener::collectDescendantsIf(LLSD const &data) { Response response(LLSD(), data); LLUUID folder_id(data["folder_id"].asUUID()); @@ -198,20 +322,63 @@ void LLInventoryListener::collectDescendentsIf(LLSD const &data) { return response.error(stringize("Folder ", std::quoted(data["folder_id"].asString()), " was not found")); } - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; + auto catresult = new CatResultSet; + auto itemresult = new ItemResultSet; LLFilteredCollector collector = LLFilteredCollector(data); - gInventory.collectDescendentsIf(folder_id, cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH, collector); + // Populate results directly into the catresult and itemresult arrays. + // TODO: sprinkle count-based coroutine yields into the real + // collectDescendentsIf() method so it doesn't steal too many cycles. + gInventory.collectDescendentsIf( + folder_id, + catresult->mCategories, + itemresult->mItems, + LLInventoryModel::EXCLUDE_TRASH, + collector); + + response["categories"] = catresult->getKey(); + response["items"] = itemresult->getKey(); +} + +/*==========================================================================*| +void LLInventoryListener::getSingle(LLSD const& data) +{ + auto result = InvResultSet::getInstance(data["result"]); + sendReply(llsd::map("single", result->getSingle(data["index"])), data); +} +|*==========================================================================*/ - add_objects_info(response, cat_array, item_array); +void LLInventoryListener::getSlice(LLSD const& data) +{ + auto result = InvResultSet::getInstance(data["result"]); + int count = data.has("count")? data["count"].asInteger() : MAX_ITEM_LIMIT; + LL_DEBUGS("Lua") << *result << ".getSlice(" << data["index"].asInteger() + << ", " << count << ')' << LL_ENDL; + sendReply(llsd::map("slice", result->getSlice(data["index"], count)), data); +} + +void LLInventoryListener::closeResult(LLSD const& data) +{ + LLSD results = data["result"]; + if (results.isInteger()) + { + results = llsd::array(results); + } + for (const auto& result : llsd::inArray(results)) + { + auto ptr = InvResultSet::getInstance(result); + if (ptr) + { + delete ptr.get(); + } + } } LLFilteredCollector::LLFilteredCollector(LLSD const &data) : mType(LLAssetType::EType::AT_UNKNOWN), mLinkFilter(INCLUDE_LINKS), - mItemLimit(MAX_ITEM_LIMIT), + mItemLimit(0), mItemCount(0) { @@ -235,7 +402,7 @@ LLFilteredCollector::LLFilteredCollector(LLSD const &data) : } if (data["limit"].isInteger()) { - mItemLimit = llclamp(data["limit"].asInteger(), 1, MAX_ITEM_LIMIT); + mItemLimit = std::max(data["limit"].asInteger(), 1); } } diff --git a/indra/newview/llinventorylistener.h b/indra/newview/llinventorylistener.h index 5cbac2ca32..a05385f2c8 100644 --- a/indra/newview/llinventorylistener.h +++ b/indra/newview/llinventorylistener.h @@ -40,8 +40,13 @@ private: void getFolderTypeNames(LLSD const &data); void getAssetTypeNames(LLSD const &data); void getBasicFolderID(LLSD const &data); - void getDirectDescendents(LLSD const &data); - void collectDescendentsIf(LLSD const &data); + void getDirectDescendants(LLSD const &data); + void collectDescendantsIf(LLSD const &data); +/*==========================================================================*| + void getSingle(LLSD const& data); +|*==========================================================================*/ + void getSlice(LLSD const& data); + void closeResult(LLSD const& data); }; #endif // LL_LLINVENTORYLISTENER_H diff --git a/indra/newview/scripts/lua/require/LLInventory.lua b/indra/newview/scripts/lua/require/LLInventory.lua index dd1b910250..0ff6b9fb37 100644 --- a/indra/newview/scripts/lua/require/LLInventory.lua +++ b/indra/newview/scripts/lua/require/LLInventory.lua @@ -1,12 +1,66 @@ local leap = require 'leap' local mapargs = require 'mapargs' +local function result(keys) + return LL.setdtor( + 'LLInventory result', + setmetatable( + -- the basic table wrapped by setmetatable just captures the int + -- result-set keys from 'keys', but with underscore prefixes + { + _categories=keys.categories, + _items=keys.items, + -- call result:close() to release result sets before garbage + -- collection or script completion + close = function(self) + leap.send('LLInventory', + {op='closeResult', + result={self._categories, self._items}}) + end + }, + -- The caller of one of our methods that returns a result set + -- isn't necessarily interested in both categories and items, so + -- don't proactively populate both. Instead, when caller references + -- either 'categories' or 'items', the __index() metamethod + -- populates that field. + { + __index = function(t, key) + -- we really don't care about references to any other field + if not table.find({'categories', 'items'}, key) then + return nil + end + -- We cleverly saved the int result set key in a field + -- with the same name but an underscore prefix. + local resultkey = t['_' .. key] + -- TODO: This only ever fetches the FIRST slice. What we + -- really want is to return a table with metamethods that + -- manage indexed access and table iteration. + -- Remember our C++ entry point uses 0-relative indexing. + local slice = leap.request( + 'LLInventory', + {op='getSlice', result=resultkey, index=0}).slice + print(`getSlice({resultkey}, 0) => {slice} ({#slice} entries)`) + -- cache this slice for future reference + t[key] = slice + return slice + end + } + ), + -- When the table-with-metatable above is destroyed, tell LLInventory + -- we're done with its result sets -- whether or not we ever fetched + -- either of them. + function(keys) + keys:close() + end + ) +end + local LLInventory = {} -- Get the items/folders info by provided IDs, -- reply will contain "items" and "categories" tables accordingly function LLInventory.getItemsInfo(item_ids) - return leap.request('LLInventory', {op = 'getItemsInfo', item_ids=item_ids}) + return result(leap.request('LLInventory', {op = 'getItemsInfo', item_ids=item_ids})) end -- Get the table of folder type names, which can be later used to get the ID of the basic folders @@ -19,30 +73,33 @@ function LLInventory.getBasicFolderID(ft_name) return leap.request('LLInventory', {op = 'getBasicFolderID', ft_name=ft_name}).id end --- Get the table of asset type names, which can be later used to get the specific items via LLInventory.collectDescendentsIf(...) +-- Get the table of asset type names, which can be later used to get the specific items via LLInventory.collectDescendantsIf(...) function LLInventory.getAssetTypeNames() return leap.request('LLInventory', {op = 'getAssetTypeNames'}).names end --- Get the direct descendents of the 'folder_id' provided, +-- Get the direct descendants of the 'folder_id' provided, -- reply will contain "items" and "categories" tables accordingly -function LLInventory.getDirectDescendents(folder_id) - return leap.request('LLInventory', {op = 'getDirectDescendents', folder_id=folder_id}) +function LLInventory.getDirectDescendants(folder_id) + return result(leap.request('LLInventory', {op = 'getDirectDescendants', folder_id=folder_id})) end +-- backwards compatibility +LLInventory.getDirectDescendents = LLInventory.getDirectDescendants --- Get the descendents of the 'folder_id' provided, which pass specified filters +-- Get the descendants of the 'folder_id' provided, which pass specified filters -- reply will contain "items" and "categories" tables accordingly --- LLInventory.collectDescendentsIf{ folder_id -- parent folder ID +-- LLInventory.collectDescendantsIf{ folder_id -- parent folder ID -- [, name] -- name (substring) -- [, desc] -- description (substring) -- [, type] -- asset type -- [, limit] -- item count limit in reply, maximum and default is 100 -- [, filter_links]} -- EXCLUDE_LINKS - don't show links, ONLY_LINKS - only show links, INCLUDE_LINKS - show links too (default) -function LLInventory.collectDescendentsIf(...) +function LLInventory.collectDescendantsIf(...) local args = mapargs('folder_id,name,desc,type,filter_links,limit', ...) - args.op = 'collectDescendentsIf' - return leap.request('LLInventory', args) + args.op = 'collectDescendantsIf' + return result(leap.request('LLInventory', args)) end - +-- backwards compatibility +LLInventory.collectDescendentsIf = LLInventory.collectDescendantsIf return LLInventory -- cgit v1.2.3 From 15db5010a0330bcb2ca2d0e4125ac3374f22b9cf Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 31 Aug 2024 12:37:57 -0400 Subject: Make global pairs(), ipairs() honor metamethods. Specifically, make pairs(obj) honor obj's __iter() metamethod if any. Make ipairs(obj) honor obj's __index() metamethod, if any. Given the semantics of the __index() metamethod, though, this only works for a proxy table if the proxy has no array entries (int keys) of its own. --- indra/llcommon/lua_function.cpp | 132 +++++++++++++++++++++++++++++++++++----- 1 file changed, 116 insertions(+), 16 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 850dff1a33..e4758d25a4 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -496,8 +496,19 @@ namespace using LuaStateMap = std::unordered_map; static LuaStateMap sLuaStateMap; -// replacement next(), pairs(), ipairs() that understand setdtor() proxy args +// replace table-at-index[name] with passed func, +// binding the original table-at-index[name] as func's upvalue +void replace_entry(lua_State* L, int index, + const std::string& name, lua_CFunction func); + +// replacement next() function that understands setdtor() proxy args int lua_proxydrill(lua_State* L); +// replacement pairs() function that supports __iter() metamethod +int lua_metapairs(lua_State* L); +// replacement ipairs() function that supports __index() metamethod +int lua_metaipairs(lua_State* L); +// helper for lua_metaipairs() (actual generator function) +int lua_metaipair(lua_State* L); } // anonymous namespace @@ -521,25 +532,114 @@ LuaState::LuaState(script_finished_fn cb): // LL.setdtor() proxy objects. // (We could also do this for selected library functions as well, // e.g. the table, string, math libraries... let's see if needed.) - for (const auto& func : { "next", "pairs", "ipairs" }) + replace_entry(mState, LUA_GLOBALSINDEX, "next", lua_proxydrill); + // Replacing pairs() with lua_metapairs() makes global pairs() honor + // objects with __iter() metamethods. + replace_entry(mState, LUA_GLOBALSINDEX, "pairs", lua_metapairs); + // Replacing ipairs() with lua_metaipairs() makes global ipairs() honor + // objects with __index() metamethods -- as long as the object in question + // has no array entries (int keys) of its own. (If it does, object[i] will + // retrieve table[i] instead of calling __index(table, i).) + replace_entry(mState, LUA_GLOBALSINDEX, "ipairs", lua_metaipairs); +} + +namespace +{ + +void replace_entry(lua_State* L, int index, + const std::string& name, lua_CFunction func) +{ + index = lua_absindex(L, index); + lua_checkdelta(L); + // push the function's name string twice + lua_pushlstring(L, name.data(), name.length()); + lua_pushvalue(L, -1); + // stack: name, name + // look up the existing table entry + lua_rawget(L, index); + // stack: name, original function + // bind original function as the upvalue for func() + lua_pushcclosure(L, func, (name + "()").c_str(), 1); + // stack: name, func-with-bound-original + // table[name] = func-with-bound-original + lua_rawset(L, index); +} + +int lua_metapairs(lua_State* L) +{ + // pairs(obj): object is at index 1 + // discard any erroneous surplus parameters + lua_settop(L, 1); + // stack: obj + if (luaL_getmetafield(L, 1, "__iter")) + { + // stack: obj, getmetatable(obj).__iter + lua_insert(L, 1); + // stack: __iter, obj + // We don't use the even nicer shorthand luaL_callmeta() because + // luaL_callmeta() only permits the metamethod to return a single + // value, and __iter() returns up to 3. Use lua_call() instead. + lua_call(L, 1, LUA_MULTRET); + // return as many values as __iter(obj) returned + return lua_gettop(L); + } + // otherwise, just return (next, obj) + lluau_checkstack(L, 1); + // stack: obj + lua_getglobal(L, "next"); + // stack: obj, next + lua_insert(L, 1); + // stack: next, obj + return 2; +} + +int lua_metaipairs(lua_State* L) +{ + lua_checkdelta(L, 2); + // ipairs(obj): object is at index 1 + // discard any erroneous surplus parameters + lua_settop(L, 1); + // stack: obj + lua_pushcfunction(L, lua_metaipair, "lua_metaipair"); + // stack: obj, lua_metaipair + lua_insert(L, 1); + // stack: lua_metaipair, obj + // push explicit 0 so lua_metaipair need not special-case nil + lua_pushinteger(L, 0); + // stack: lua_metaipair, obj, 0 + return 3; +} + +int lua_metaipair(lua_State* L) +{ + // called with (obj, previous-index) + // increment previous-index for this call + lua_Integer i = luaL_checkinteger(L, 2) + 1; + lua_pop(L, 1); + // stack: obj + lua_pushinteger(L, i); + // stack: obj, i + lua_pushvalue(L, -1); + // stack: obj, i, i + lua_insert(L, 1); + // stack: i, obj, i + lua_gettable(L, -2); + // stack: i, obj, obj[i] + lua_remove(L, -2); + // stack: i, obj[i] + if (! lua_isnil(L, -1)) { - // push the function's name string twice - lua_pushstring(mState, func); - lua_pushvalue(mState, -1); - // stack: name, name - // look up the existing global - lua_rawget(mState, LUA_GLOBALSINDEX); - // stack: name, global function - // bind original function as the upvalue for lua_proxydrill() - lua_pushcclosure(mState, lua_proxydrill, - stringize("lua_proxydrill(", func, ')').c_str(), - 1); - // stack: name, lua_proxydrill(func) - // global name = lua_proxydrill(func) - lua_rawset(mState, LUA_GLOBALSINDEX); + // great, obj[i] isn't nil: return (i, obj[i]) + return 2; } + // obj[i] is nil. ipairs() is documented to stop at the first hole, + // regardless of #obj. Clear the stack, i.e. return nil. + lua_settop(L, 0); + return 0; } +} // anonymous namespace + LuaState::~LuaState() { // We're just about to destroy this lua_State mState. Did this Lua chunk -- cgit v1.2.3 From 8c18cfd22583e981f93734ec0aa6ee0ead3f26c5 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 2 Sep 2024 15:26:34 -0400 Subject: Make `pairs()`, `ipairs()` forward to original funcs if no metamethods. That is, our replacement `pairs()` forwards the call to built-in `pairs()` when the passed object has no `__iter()` metamethod. Similarly, our replacement `ipairs()` forwards to built-in `ipairs()` when the passed object has no `__index()` metamethod. This allows for the possibility that the built-in `pairs()` and `ipairs()` functions engage more efficient implementations than the obvious ones. --- indra/llcommon/lua_function.cpp | 87 +++++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 33 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index e4758d25a4..da88f57a5b 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -568,46 +568,67 @@ void replace_entry(lua_State* L, int index, int lua_metapairs(lua_State* L) { // pairs(obj): object is at index 1 - // discard any erroneous surplus parameters - lua_settop(L, 1); - // stack: obj + // How many args were we passed? + int args = lua_gettop(L); + // stack: obj, ... if (luaL_getmetafield(L, 1, "__iter")) { - // stack: obj, getmetatable(obj).__iter - lua_insert(L, 1); - // stack: __iter, obj - // We don't use the even nicer shorthand luaL_callmeta() because - // luaL_callmeta() only permits the metamethod to return a single - // value, and __iter() returns up to 3. Use lua_call() instead. - lua_call(L, 1, LUA_MULTRET); - // return as many values as __iter(obj) returned - return lua_gettop(L); + // stack: obj, ..., getmetatable(obj).__iter + } + else + { + // Push the original pairs() function, captured as our upvalue. + lua_pushvalue(L, lua_upvalueindex(1)); + // stack: obj, ..., original pairs() } - // otherwise, just return (next, obj) - lluau_checkstack(L, 1); - // stack: obj - lua_getglobal(L, "next"); - // stack: obj, next lua_insert(L, 1); - // stack: next, obj - return 2; + // stack: (__iter() or pairs()), obj, ... + // call whichever function(obj, ...) (args args, up to 3 return values) + lua_call(L, args, LUA_MULTRET); + // return as many values as the selected function returned + return lua_gettop(L); } int lua_metaipairs(lua_State* L) { - lua_checkdelta(L, 2); // ipairs(obj): object is at index 1 - // discard any erroneous surplus parameters - lua_settop(L, 1); - // stack: obj - lua_pushcfunction(L, lua_metaipair, "lua_metaipair"); - // stack: obj, lua_metaipair - lua_insert(L, 1); - // stack: lua_metaipair, obj - // push explicit 0 so lua_metaipair need not special-case nil - lua_pushinteger(L, 0); - // stack: lua_metaipair, obj, 0 - return 3; + // How many args were we passed? + int args = lua_gettop(L); + // stack: obj, ... + if (luaL_getmetafield(L, 1, "__index")) + { + // stack: obj, ..., getmetatable(obj).__index + // discard __index and everything but obj: + // we don't want to call __index(), just check its presence + lua_settop(L, 1); + // stack: obj + lua_pushcfunction(L, lua_metaipair, "lua_metaipair"); + // stack: obj, lua_metaipair + lua_insert(L, 1); + // stack: lua_metaipair, obj + // push explicit 0 so lua_metaipair need not special-case nil + lua_pushinteger(L, 0); + // stack: lua_metaipair, obj, 0 + return 3; + } + else // no __index() metamethod + { + // Although our lua_metaipair() function demonstrably works whether or + // not our object has an __index() metamethod, the code below assumes + // that the Lua engine may have a more efficient implementation for + // built-in ipairs() than our lua_metaipair(). + // Push the original ipairs() function, captured as our upvalue. + lua_pushvalue(L, lua_upvalueindex(1)); + // stack: obj, ..., original ipairs() + // Shift the stack so the original function is first. + lua_insert(L, 1); + // stack: original ipairs(), obj, ... + // Call original ipairs() with all original args, no error checking. + // Don't truncate however many values that function returns. + lua_call(L, args, LUA_MULTRET); + // Return as many values as the original function returned. + return lua_gettop(L); + } } int lua_metaipair(lua_State* L) @@ -624,7 +645,7 @@ int lua_metaipair(lua_State* L) lua_insert(L, 1); // stack: i, obj, i lua_gettable(L, -2); - // stack: i, obj, obj[i] + // stack: i, obj, obj[i] (honoring __index()) lua_remove(L, -2); // stack: i, obj[i] if (! lua_isnil(L, -1)) @@ -1388,7 +1409,7 @@ int setdtor_refs::meta__index(lua_State* L) return 1; } -// replacement for global next(), pairs(), ipairs(): +// replacement for global next(): // its lua_upvalueindex(1) is the original function it's replacing int lua_proxydrill(lua_State* L) { -- cgit v1.2.3 From 1101ed699a0c3c23c0bb11267d390febfdc02409 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 2 Sep 2024 16:41:09 -0400 Subject: Introduce result_view.lua, and use it in LLInventory.lua. result_view(key_length, fetch) returns a virtual view of a potentially-large C++ result set. Given the result-set key, its total length and a function fetch(key, start) => (slice, adjusted start), the read-only table returned by result_view() manages indexed access and table iteration over the entire result set, fetching a slice at a time as required. Change LLInventory to use result_view() instead of only ever fetching the first slice of a result set. TODO: This depends on the viewer's "LLInventory" listener returning the total result set length as well as the result set key. It does not yet return the length. --- indra/newview/scripts/lua/require/LLInventory.lua | 38 +++++++------- indra/newview/scripts/lua/require/result_view.lua | 62 +++++++++++++++++++++++ 2 files changed, 82 insertions(+), 18 deletions(-) create mode 100644 indra/newview/scripts/lua/require/result_view.lua (limited to 'indra') diff --git a/indra/newview/scripts/lua/require/LLInventory.lua b/indra/newview/scripts/lua/require/LLInventory.lua index 0ff6b9fb37..ce501e75f3 100644 --- a/indra/newview/scripts/lua/require/LLInventory.lua +++ b/indra/newview/scripts/lua/require/LLInventory.lua @@ -1,12 +1,14 @@ local leap = require 'leap' local mapargs = require 'mapargs' +local result_view = require 'result_view' local function result(keys) return LL.setdtor( 'LLInventory result', setmetatable( -- the basic table wrapped by setmetatable just captures the int - -- result-set keys from 'keys', but with underscore prefixes + -- result-set {key, length} pairs from 'keys', but with underscore + -- prefixes { _categories=keys.categories, _items=keys.items, @@ -15,7 +17,7 @@ local function result(keys) close = function(self) leap.send('LLInventory', {op='closeResult', - result={self._categories, self._items}}) + result={self._categories[1], self._items[1]}}) end }, -- The caller of one of our methods that returns a result set @@ -24,25 +26,25 @@ local function result(keys) -- either 'categories' or 'items', the __index() metamethod -- populates that field. { - __index = function(t, key) + __index = function(t, field) -- we really don't care about references to any other field - if not table.find({'categories', 'items'}, key) then + if not table.find({'categories', 'items'}, field) then return nil end - -- We cleverly saved the int result set key in a field - -- with the same name but an underscore prefix. - local resultkey = t['_' .. key] - -- TODO: This only ever fetches the FIRST slice. What we - -- really want is to return a table with metamethods that - -- manage indexed access and table iteration. - -- Remember our C++ entry point uses 0-relative indexing. - local slice = leap.request( - 'LLInventory', - {op='getSlice', result=resultkey, index=0}).slice - print(`getSlice({resultkey}, 0) => {slice} ({#slice} entries)`) - -- cache this slice for future reference - t[key] = slice - return slice + local view = result_view( + -- We cleverly saved the result set {key, length} pair in + -- a field with the same name but an underscore prefix. + t['_' .. field], + function(key, start) + local fetched = leap.request( + 'LLInventory', + {op='getSlice', result=key, index=start}) + return fetched.slice, fetched.start + end + ) + -- cache that view for future reference + t[field] = view + return view end } ), diff --git a/indra/newview/scripts/lua/require/result_view.lua b/indra/newview/scripts/lua/require/result_view.lua new file mode 100644 index 0000000000..4a58636f2f --- /dev/null +++ b/indra/newview/scripts/lua/require/result_view.lua @@ -0,0 +1,62 @@ +-- result_view(key_length, fetch) returns a table which stores only a slice +-- of a result set plus some control values, yet presents read-only virtual +-- access to the entire result set. +-- key_length: {result set key, total result set length} +-- fetch: function(key, start) that returns (slice, adjusted start) +local function result_view(key_length, fetch) + return setmetatable( + { + key=key_length[1], + length=key_length[2], + -- C++ result sets use 0-based indexing, so internally we do too + start=0, + -- start with a dummy array with length 0 + slice={} + }, + { + __len = function(this) + return this.length + end, + __index = function(this, i) + -- right away, convert to 0-relative indexing + i -= 1 + -- can we find this index within the current slice? + local reli = i - this.start + if 0 <= reli and reli < #this.slice then + return this.slice[reli] + end + -- is this index outside the overall result set? + if not (0 <= i and i < this.length) then + return nil + end + -- fetch a new slice starting at i, using provided fetch() + local start + this.slice, start = fetch(key, i) + -- It's possible that caller-provided fetch() function forgot + -- to return the adjusted start index of the new slice. In + -- Lua, 0 tests as true, so if fetch() returned (slice, 0), + -- we'll duly reset this.start to 0. + if start then + this.start = start + end + -- hopefully this slice contains the desired i + return this.slice[i - this.start] + end, + -- We purposely avoid putting any array entries (int keys) into + -- our table so that access to any int key will always call our + -- __index() metamethod. Moreover, we want any table iteration to + -- call __index(table, i) however many times; we do NOT want it to + -- retrieve key, length, start, slice. + -- So turn 'for k, v in result' into 'for k, v in ipairs(result)'. + __iter = ipairs, + -- This result set provides read-only access. + -- We do not support pushing updates to individual items back to + -- C++; for the intended use cases, that makes no sense. + __newindex = function(this, i, value) + error("result_view is a read-only data structure", 2) + end + } + ) +end + +return result_view -- cgit v1.2.3 From 9dc916bfcafd43890be20623d359be82e84f73ac Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Sep 2024 12:28:25 -0400 Subject: In lua_what() and lua_stack(), try to report a function's name. --- indra/llcommon/lua_function.cpp | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index da88f57a5b..67ca29c689 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -567,6 +567,7 @@ void replace_entry(lua_State* L, int index, int lua_metapairs(lua_State* L) { +// LuaLog debug(L, "lua_metapairs()"); // pairs(obj): object is at index 1 // How many args were we passed? int args = lua_gettop(L); @@ -591,6 +592,7 @@ int lua_metapairs(lua_State* L) int lua_metaipairs(lua_State* L) { +// LuaLog debug(L, "lua_metaipairs()"); // ipairs(obj): object is at index 1 // How many args were we passed? int args = lua_gettop(L); @@ -633,9 +635,10 @@ int lua_metaipairs(lua_State* L) int lua_metaipair(lua_State* L) { +// LuaLog debug(L, "lua_metaipair()"); // called with (obj, previous-index) // increment previous-index for this call - lua_Integer i = luaL_checkinteger(L, 2) + 1; + lua_Integer i = luaL_optinteger(L, 2, 0) + 1; lua_pop(L, 1); // stack: obj lua_pushinteger(L, i); @@ -1523,6 +1526,31 @@ std::ostream& operator<<(std::ostream& out, const lua_what& self) out << lua_touserdata(self.L, self.index); break; + case LUA_TFUNCTION: + { + // Try for the function's name, at the cost of a few more stack + // entries. + lua_checkdelta(self.L); + lluau_checkstack(self.L, 3); + lua_getglobal(self.L, "debug"); + // stack: ..., debug + lua_getfield(self.L, -1, "info"); + // stack: ..., debug, debug.info + lua_remove(self.L, -2); + // stack: ..., debug.info + lua_pushvalue(self.L, self.index); + // stack: ..., debug.info, this function + lua_pushstring(self.L, "n"); + // stack: ..., debug.info, this function, "n" + // 2 arguments, 1 return value (or error message), no error handler + lua_pcall(self.L, 2, 1, 0); + // stack: ..., function name (or error) from debug.info() + out << "function " << lua_tostdstring(self.L, -1); + lua_pop(self.L, 1); + // stack: ... + break; + } + default: // anything else, don't bother trying to report value, just type out << lua_typename(self.L, lua_type(self.L, self.index)); -- cgit v1.2.3 From 6a4b9b1184c142ca1b317296fa12304bf231fc7d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Sep 2024 12:34:21 -0400 Subject: Add test_result_view.lua; fix minor bugs in result_view.lua. --- indra/newview/scripts/lua/require/result_view.lua | 18 ++++---- indra/newview/scripts/lua/test_result_view.lua | 55 +++++++++++++++++++++++ 2 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 indra/newview/scripts/lua/test_result_view.lua (limited to 'indra') diff --git a/indra/newview/scripts/lua/require/result_view.lua b/indra/newview/scripts/lua/require/result_view.lua index 4a58636f2f..d53d953c24 100644 --- a/indra/newview/scripts/lua/require/result_view.lua +++ b/indra/newview/scripts/lua/require/result_view.lua @@ -23,7 +23,8 @@ local function result_view(key_length, fetch) -- can we find this index within the current slice? local reli = i - this.start if 0 <= reli and reli < #this.slice then - return this.slice[reli] + -- Lua 1-relative indexing + return this.slice[reli + 1] end -- is this index outside the overall result set? if not (0 <= i and i < this.length) then @@ -31,16 +32,17 @@ local function result_view(key_length, fetch) end -- fetch a new slice starting at i, using provided fetch() local start - this.slice, start = fetch(key, i) + this.slice, start = fetch(this.key, i) -- It's possible that caller-provided fetch() function forgot -- to return the adjusted start index of the new slice. In -- Lua, 0 tests as true, so if fetch() returned (slice, 0), - -- we'll duly reset this.start to 0. - if start then - this.start = start - end - -- hopefully this slice contains the desired i - return this.slice[i - this.start] + -- we'll duly reset this.start to 0. Otherwise, assume the + -- requested index was not adjusted: that the returned slice + -- really does start at i. + this.start = start or i + -- Hopefully this slice contains the desired i. + -- Back to 1-relative indexing. + return this.slice[i - this.start + 1] end, -- We purposely avoid putting any array entries (int keys) into -- our table so that access to any int key will always call our diff --git a/indra/newview/scripts/lua/test_result_view.lua b/indra/newview/scripts/lua/test_result_view.lua new file mode 100644 index 0000000000..304633a472 --- /dev/null +++ b/indra/newview/scripts/lua/test_result_view.lua @@ -0,0 +1,55 @@ +-- Verify the functionality of result_view. +result_view = require 'result_view' + +print('alphabet') +alphabet = "abcdefghijklmnopqrstuvwxyz" +assert(#alphabet == 26) +alphabits = string.split(alphabet, '') + +print('function slice()') +function slice(t, index, count) + return table.move(t, index, index + count - 1, 1, {}) +end + +print('verify slice()') +-- verify that slice() does what we expect +assert(table.concat(slice(alphabits, 4, 3)) == "def") +assert(table.concat(slice(alphabits, 14, 3)) == "nop") +assert(table.concat(slice(alphabits, 25, 3)) == "yz") + +print('function fetch()') +function fetch(key, index) + -- fetch function is defined to be 0-relative: fix for Lua data + -- constrain view of alphabits to slices of at most 3 elements + return slice(alphabits, index+1, 3), index +end + +print('result_view()') +-- for test purposes, key is irrelevant, so just 'key' +view = result_view({'key', #alphabits}, fetch) + +print('function check_iter()') +function check_iter(...) + result = {} + for k, v in ... do + table.insert(result, v) + end + assert(table.concat(result) == alphabet) +end + +print('check_iter(pairs(view))') +check_iter(pairs(view)) +print('check_iter(ipairs(view))') +check_iter(ipairs(view)) +print('check_iter(view)') +check_iter(view) + +print('raw index access') +assert(view[5] == 'e') +assert(view[10] == 'j') +assert(view[15] == 'o') +assert(view[20] == 't') +assert(view[25] == 'y') + +print('Success!') + -- cgit v1.2.3 From ab04d116cc47fa979018525fce4f11b379aeeec5 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Sep 2024 12:35:55 -0400 Subject: Break out llinventorylistener.cpp's InvResultSet as LL::ResultSet. We may well want to leverage that API for additional queries that could potentially return large datasets. --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/resultset.cpp | 93 ++++++++++++++++++++++++++++ indra/llcommon/resultset.h | 61 +++++++++++++++++++ indra/newview/llinventorylistener.cpp | 111 ++++++---------------------------- 4 files changed, 175 insertions(+), 92 deletions(-) create mode 100644 indra/llcommon/resultset.cpp create mode 100644 indra/llcommon/resultset.h (limited to 'indra') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 8577d94be1..8c346ea6ce 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -112,6 +112,7 @@ set(llcommon_SOURCE_FILES lockstatic.cpp lua_function.cpp lualistener.cpp + resultset.cpp threadpool.cpp throttle.cpp u64.cpp @@ -261,6 +262,7 @@ set(llcommon_HEADER_FILES lockstatic.h lua_function.h lualistener.h + resultset.h stdtypes.h stringize.h tempset.h diff --git a/indra/llcommon/resultset.cpp b/indra/llcommon/resultset.cpp new file mode 100644 index 0000000000..ee8cc68c6c --- /dev/null +++ b/indra/llcommon/resultset.cpp @@ -0,0 +1,93 @@ +/** + * @file resultset.cpp + * @author Nat Goodspeed + * @date 2024-09-03 + * @brief Implementation for resultset. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "resultset.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llerror.h" +#include "llsdutil.h" + +namespace LL +{ + +LLSD ResultSet::getKeyLength() const +{ + return llsd::array(getKey(), getLength()); +} + +std::pair ResultSet::getSliceStart(int index, int count) const +{ + // only call getLength() once + auto length = getLength(); + // Adjust bounds [start, end) to overlap the actual result set from + // [0, getLength()). Permit negative index; e.g. with a result set + // containing 5 entries, getSlice(-2, 5) will adjust start to 0 and + // end to 3. + int start = llclamp(index, 0, length); + int end = llclamp(index + count, 0, length); + LLSD result{ LLSD::emptyArray() }; + // beware of count <= 0, or an [index, count) range that doesn't even + // overlap [0, length) at all + if (end > start) + { + // right away expand the result array to the size we'll need + result[end - 1] = LLSD(); + for (int i = start; i < end; ++i) + { + result[i] = getSingle(i); + } + } + return { result, start }; +} + +LLSD ResultSet::getSlice(int index, int count) const +{ + return getSliceStart(index, count).first; +} + +/*==========================================================================*| +LLSD ResultSet::getSingle(int index) const +{ + if (0 <= index && index < getLength()) + { + return getSingle_(index); + } + else + { + return {}; + } +} +|*==========================================================================*/ + +ResultSet::ResultSet(const std::string& name): + mName(name) +{ + LL_DEBUGS("Lua") << *this << LL_ENDL; +} + +ResultSet::~ResultSet() +{ + // We want to be able to observe that the consuming script eventually + // destroys each of these ResultSets. + LL_DEBUGS("Lua") << "~" << *this << LL_ENDL; +} + +} // namespace LL + +std::ostream& operator<<(std::ostream& out, const LL::ResultSet& self) +{ + return out << "ResultSet(" << self.mName << ", " << self.getKey() << ")"; +} diff --git a/indra/llcommon/resultset.h b/indra/llcommon/resultset.h new file mode 100644 index 0000000000..9b9ecbb21e --- /dev/null +++ b/indra/llcommon/resultset.h @@ -0,0 +1,61 @@ +/** + * @file resultset.h + * @author Nat Goodspeed + * @date 2024-09-03 + * @brief ResultSet is an abstract base class to allow scripted access to + * potentially large collections representable as LLSD arrays. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_RESULTSET_H) +#define LL_RESULTSET_H + +#include "llinttracker.h" +#include "llsd.h" +#include // std::ostream +#include // std::pair + +namespace LL +{ + +// This abstract base class defines an interface by which a large collection +// of items representable as an LLSD array can be retrieved in slices. It isa +// LLIntTracker so we can pass its unique int key to a consuming script via +// LLSD. +struct ResultSet: public LLIntTracker +{ + // Get the length of the result set. Indexes are 0-relative. + virtual int getLength() const = 0; + // Get conventional LLSD { key, length } pair. + LLSD getKeyLength() const; + // Retrieve LLSD corresponding to a single entry from the result set, + // once we're sure the index is valid. + virtual LLSD getSingle(int index) const = 0; + // Retrieve LLSD corresponding to a "slice" of the result set: a + // contiguous sub-array starting at index. The returned LLSD array might + // be shorter than count entries if count > MAX_ITEM_LIMIT, or if the + // specified slice contains the end of the result set. + LLSD getSlice(int index, int count) const; + // Like getSlice(), but also return adjusted start position. + std::pair getSliceStart(int index, int count) const; +/*==========================================================================*| + // Retrieve LLSD corresponding to a single entry from the result set, + // with index validation. + LLSD getSingle(int index) const; +|*==========================================================================*/ + + /*---------------- the rest is solely for debug logging ----------------*/ + std::string mName; + + ResultSet(const std::string& name); + virtual ~ResultSet(); +}; + +} // namespace LL + +std::ostream& operator<<(std::ostream& out, const LL::ResultSet& self); + +#endif /* ! defined(LL_RESULTSET_H) */ diff --git a/indra/newview/llinventorylistener.cpp b/indra/newview/llinventorylistener.cpp index c330ef42a0..6a8182c539 100644 --- a/indra/newview/llinventorylistener.cpp +++ b/indra/newview/llinventorylistener.cpp @@ -28,13 +28,14 @@ #include "llinventorylistener.h" #include "llappearancemgr.h" -#include "llinttracker.h" #include "llinventoryfunctions.h" #include "lltransutil.h" #include "llwearableitemslist.h" +#include "resultset.h" #include "stringize.h" +#include // std::min() -constexpr U32 MAX_ITEM_LIMIT = 100; +constexpr S32 MAX_ITEM_LIMIT = 100; LLInventoryListener::LLInventoryListener() : LLEventAPI("LLInventory", @@ -104,87 +105,11 @@ LLInventoryListener::LLInventoryListener() llsd::map("result", LLSD())); } -// This abstract base class defines the interface for CatResultSet and -// ItemResultSet. It isa LLIntTracker so we can pass its unique int key to a -// consuming script via LLSD. -struct InvResultSet: public LLIntTracker -{ - // Get the length of the result set. Indexes are 0-relative. - virtual int getLength() const = 0; -/*==========================================================================*| - // Retrieve LLSD corresponding to a single entry from the result set, - // with index validation. - LLSD getSingle(int index) const - { - if (0 <= index && index < getLength()) - { - return getSingle_(index); - } - else - { - return {}; - } - } -|*==========================================================================*/ - // Retrieve LLSD corresponding to a single entry from the result set, - // once we're sure the index is valid. - virtual LLSD getSingle(int index) const = 0; - // Retrieve LLSD corresponding to a "slice" of the result set: a - // contiguous sub-array starting at index. The returned LLSD array might - // be shorter than count entries if count > MAX_ITEM_LIMIT, or if the - // specified slice contains the end of the result set. - LLSD getSlice(int index, int count) const - { - // only call getLength() once - auto length = getLength(); - // Adjust bounds [start, end) to overlap the actual result set from - // [0, getLength()). Permit negative index; e.g. with a result set - // containing 5 entries, getSlice(-2, 5) will adjust start to 0 and - // end to 3. - int start = llclamp(index, 0, length); - // Constrain count to MAX_ITEM_LIMIT even before clamping end. - int end = llclamp(index + llclamp(count, 0, MAX_ITEM_LIMIT), 0, length); - LLSD result{ LLSD::emptyArray() }; - // beware of count == 0, or an [index, count) range that doesn't even - // overlap [0, length) at all - if (end > start) - { - // right away expand the result array to the size we'll need - result[end - 1] = LLSD(); - for (int i = start; i < end; ++i) - { - result[i] = getSingle(i); - } - } - return result; - } - - /*---------------- the rest is solely for debug logging ----------------*/ - std::string mName; - - friend std::ostream& operator<<(std::ostream& out, const InvResultSet& self) - { - return out << "InvResultSet(" << self.mName << ", " << self.getKey() << ")"; - } - - InvResultSet(const std::string& name): - mName(name) - { - LL_DEBUGS("Lua") << *this << LL_ENDL; - } - virtual ~InvResultSet() - { - // We want to be able to observe that the consuming script uses - // LL.setdtor() to eventually destroy each of these InvResultSets. - LL_DEBUGS("Lua") << "~" << *this << LL_ENDL; - } -}; - // This struct captures (possibly large) category results from // getDirectDescendants() and collectDescendantsIf(). -struct CatResultSet: public InvResultSet +struct CatResultSet: public LL::ResultSet { - CatResultSet(): InvResultSet("categories") {} + CatResultSet(): LL::ResultSet("categories") {} LLInventoryModel::cat_array_t mCategories; int getLength() const override { return narrow(mCategories.size()); } @@ -199,9 +124,9 @@ struct CatResultSet: public InvResultSet // This struct captures (possibly large) item results from // getDirectDescendants() and collectDescendantsIf(). -struct ItemResultSet: public InvResultSet +struct ItemResultSet: public LL::ResultSet { - ItemResultSet(): InvResultSet("items") {} + ItemResultSet(): LL::ResultSet("items") {} LLInventoryModel::item_array_t mItems; int getLength() const override { return narrow(mItems.size()); } @@ -244,8 +169,9 @@ void LLInventoryListener::getItemsInfo(LLSD const &data) } } } - response["categories"] = catresult->getKey(); - response["items"] = itemresult->getKey(); + // Each of categories and items is a { result set key, total length } pair. + response["categories"] = catresult->getKeyLength(); + response["items"] = itemresult->getKeyLength(); } void LLInventoryListener::getFolderTypeNames(LLSD const &data) @@ -277,8 +203,8 @@ void LLInventoryListener::getDirectDescendants(LLSD const &data) catresult->mCategories = *cats; itemresult->mItems = *items; - response["categories"] = catresult->getKey(); - response["items"] = itemresult->getKey(); + response["categories"] = catresult->getKeyLength(); + response["items"] = itemresult->getKeyLength(); } struct LLFilteredCollector : public LLInventoryCollectFunctor @@ -337,25 +263,26 @@ void LLInventoryListener::collectDescendantsIf(LLSD const &data) LLInventoryModel::EXCLUDE_TRASH, collector); - response["categories"] = catresult->getKey(); - response["items"] = itemresult->getKey(); + response["categories"] = catresult->getKeyLength(); + response["items"] = itemresult->getKeyLength(); } /*==========================================================================*| void LLInventoryListener::getSingle(LLSD const& data) { - auto result = InvResultSet::getInstance(data["result"]); + auto result = LL::ResultSet::getInstance(data["result"]); sendReply(llsd::map("single", result->getSingle(data["index"])), data); } |*==========================================================================*/ void LLInventoryListener::getSlice(LLSD const& data) { - auto result = InvResultSet::getInstance(data["result"]); + auto result = LL::ResultSet::getInstance(data["result"]); int count = data.has("count")? data["count"].asInteger() : MAX_ITEM_LIMIT; LL_DEBUGS("Lua") << *result << ".getSlice(" << data["index"].asInteger() << ", " << count << ')' << LL_ENDL; - sendReply(llsd::map("slice", result->getSlice(data["index"], count)), data); + auto pair{ result->getSliceStart(data["index"], std::min(count, MAX_ITEM_LIMIT)) }; + sendReply(llsd::map("slice", pair.first, "start", pair.second), data); } void LLInventoryListener::closeResult(LLSD const& data) @@ -367,7 +294,7 @@ void LLInventoryListener::closeResult(LLSD const& data) } for (const auto& result : llsd::inArray(results)) { - auto ptr = InvResultSet::getInstance(result); + auto ptr = LL::ResultSet::getInstance(result); if (ptr) { delete ptr.get(); -- cgit v1.2.3 From 2157fa4fa9acf4db6093b962569274105e4d1fb4 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Sep 2024 12:52:34 -0400 Subject: result_view() now reuses same metatable instance for every table. --- indra/newview/scripts/lua/require/result_view.lua | 97 ++++++++++++----------- 1 file changed, 51 insertions(+), 46 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/require/result_view.lua b/indra/newview/scripts/lua/require/result_view.lua index d53d953c24..c719681c66 100644 --- a/indra/newview/scripts/lua/require/result_view.lua +++ b/indra/newview/scripts/lua/require/result_view.lua @@ -1,3 +1,50 @@ +-- metatable for every result_view() table +local mt = { + __len = function(this) + return this.length + end, + __index = function(this, i) + -- right away, convert to 0-relative indexing + i -= 1 + -- can we find this index within the current slice? + local reli = i - this.start + if 0 <= reli and reli < #this.slice then + -- Lua 1-relative indexing + return this.slice[reli + 1] + end + -- is this index outside the overall result set? + if not (0 <= i and i < this.length) then + return nil + end + -- fetch a new slice starting at i, using provided fetch() + local start + this.slice, start = this.fetch(this.key, i) + -- It's possible that caller-provided fetch() function forgot + -- to return the adjusted start index of the new slice. In + -- Lua, 0 tests as true, so if fetch() returned (slice, 0), + -- we'll duly reset this.start to 0. Otherwise, assume the + -- requested index was not adjusted: that the returned slice + -- really does start at i. + this.start = start or i + -- Hopefully this slice contains the desired i. + -- Back to 1-relative indexing. + return this.slice[i - this.start + 1] + end, + -- We purposely avoid putting any array entries (int keys) into + -- our table so that access to any int key will always call our + -- __index() metamethod. Moreover, we want any table iteration to + -- call __index(table, i) however many times; we do NOT want it to + -- retrieve key, length, start, slice. + -- So turn 'for k, v in result' into 'for k, v in ipairs(result)'. + __iter = ipairs, + -- This result set provides read-only access. + -- We do not support pushing updates to individual items back to + -- C++; for the intended use cases, that makes no sense. + __newindex = function(this, i, value) + error("result_view is a read-only data structure", 2) + end +} + -- result_view(key_length, fetch) returns a table which stores only a slice -- of a result set plus some control values, yet presents read-only virtual -- access to the entire result set. @@ -11,53 +58,11 @@ local function result_view(key_length, fetch) -- C++ result sets use 0-based indexing, so internally we do too start=0, -- start with a dummy array with length 0 - slice={} + slice={}, + fetch=fetch }, - { - __len = function(this) - return this.length - end, - __index = function(this, i) - -- right away, convert to 0-relative indexing - i -= 1 - -- can we find this index within the current slice? - local reli = i - this.start - if 0 <= reli and reli < #this.slice then - -- Lua 1-relative indexing - return this.slice[reli + 1] - end - -- is this index outside the overall result set? - if not (0 <= i and i < this.length) then - return nil - end - -- fetch a new slice starting at i, using provided fetch() - local start - this.slice, start = fetch(this.key, i) - -- It's possible that caller-provided fetch() function forgot - -- to return the adjusted start index of the new slice. In - -- Lua, 0 tests as true, so if fetch() returned (slice, 0), - -- we'll duly reset this.start to 0. Otherwise, assume the - -- requested index was not adjusted: that the returned slice - -- really does start at i. - this.start = start or i - -- Hopefully this slice contains the desired i. - -- Back to 1-relative indexing. - return this.slice[i - this.start + 1] - end, - -- We purposely avoid putting any array entries (int keys) into - -- our table so that access to any int key will always call our - -- __index() metamethod. Moreover, we want any table iteration to - -- call __index(table, i) however many times; we do NOT want it to - -- retrieve key, length, start, slice. - -- So turn 'for k, v in result' into 'for k, v in ipairs(result)'. - __iter = ipairs, - -- This result set provides read-only access. - -- We do not support pushing updates to individual items back to - -- C++; for the intended use cases, that makes no sense. - __newindex = function(this, i, value) - error("result_view is a read-only data structure", 2) - end - } + -- use our special metatable + mt ) end -- cgit v1.2.3 From 93c21b503abdf4f9530064f8c3f1df7ea0dc244f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Sep 2024 13:01:11 -0400 Subject: test_inv_resultset.lua exercises LLInventory's result-set functionality. --- indra/newview/scripts/lua/test_inv_resultset.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 indra/newview/scripts/lua/test_inv_resultset.lua (limited to 'indra') diff --git a/indra/newview/scripts/lua/test_inv_resultset.lua b/indra/newview/scripts/lua/test_inv_resultset.lua new file mode 100644 index 0000000000..c31cfe3c67 --- /dev/null +++ b/indra/newview/scripts/lua/test_inv_resultset.lua @@ -0,0 +1,18 @@ +local LLInventory = require 'LLInventory' +local inspect = require 'inspect' + +print('basic folders:') +print(inspect(LLInventory.getFolderTypeNames())) + +local folder = LLInventory.getBasicFolderID('my_otfts') +print(`folder = {folder}`) +local result = LLInventory.getDirectDescendants(folder) +print(`type(result) = {type(result)}`) +print(#result.categories, 'categories:') +for i, cat in pairs(result.categories) do + print(`{i}: {cat.name}`) +end +print(#result.items, 'items') +for i, item in pairs(result.items) do + print(`{i}: {item.name}`) +end -- cgit v1.2.3 From 83eace32cfccc672e7a5a2841bd7d844dce0ea3e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Sep 2024 13:13:22 -0400 Subject: Iterate to print landmarks returned by LLInventory. At this point, inspect(landmarks) just returns "". --- indra/newview/scripts/lua/test_LLInventory.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/test_LLInventory.lua b/indra/newview/scripts/lua/test_LLInventory.lua index 107b0791d4..918ca56a2e 100644 --- a/indra/newview/scripts/lua/test_LLInventory.lua +++ b/indra/newview/scripts/lua/test_LLInventory.lua @@ -5,7 +5,9 @@ LLInventory = require 'LLInventory' my_landmarks_id = LLInventory.getBasicFolderID('landmark') -- Get 3 landmarks from the 'My Landmarks' folder (you can see all folder types via LLInventory.getAssetTypeNames()) landmarks = LLInventory.collectDescendentsIf{folder_id=my_landmarks_id, type="landmark", limit=3} -print(inspect(landmarks)) +for _, landmark in pairs(landmarks.items) do + print(landmark.name) +end -- Get 'Calling Cards' folder id calling_cards_id = LLInventory.getBasicFolderID('callcard') -- cgit v1.2.3 From bf2b2eb01ca8680914d17dda713d9365e2ecc3eb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Sep 2024 16:26:03 -0400 Subject: Add Lua traceback to errors from calling lluau::expr(). That includes scripts run by LLLUAmanager::runScriptFile(), runScriptLine() et al. --- indra/llcommon/lua_function.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 67ca29c689..c3d336bfcb 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -65,10 +65,26 @@ int dostring(lua_State* L, const std::string& desc, const std::string& text) if (r != LUA_OK) return r; + // Push debug.traceback() onto the stack as lua_pcall()'s error + // handler function. On error, lua_pcall() calls the specified error + // handler function with the original error message; the message + // returned by the error handler is then returned by lua_pcall(). + // Luau's debug.traceback() is called with a message to prepend to the + // returned traceback string. Almost as if they'd been designed to + // work together... + lua_getglobal(L, "debug"); + lua_getfield(L, -1, "traceback"); + // ditch "debug" + lua_remove(L, -2); + // stack: compiled chunk, debug.traceback() + lua_insert(L, -2); + // stack: debug.traceback(), compiled chunk + LuaRemover cleanup(L, -2); + // It's important to pass LUA_MULTRET as the expected number of return // values: if we pass any fixed number, we discard any returned values // beyond that number. - return lua_pcall(L, 0, LUA_MULTRET, 0); + return lua_pcall(L, 0, LUA_MULTRET, -2); } int loadstring(lua_State *L, const std::string &desc, const std::string &text) -- cgit v1.2.3 From 517163c126f7c0620f506532d67d9097083728d9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Sep 2024 18:02:31 -0400 Subject: Fix a bug in ResultSet::getSliceStart(). When asked to retrieve a slice starting at an `index > 0`, `getSliceStart()` was returning an LLSD array whose first `index` entries were `isUndefined()`, followed by the desired data. Fix to omit those undefined entries. --- indra/llcommon/resultset.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/resultset.cpp b/indra/llcommon/resultset.cpp index ee8cc68c6c..cb46db04fa 100644 --- a/indra/llcommon/resultset.cpp +++ b/indra/llcommon/resultset.cpp @@ -43,11 +43,14 @@ std::pair ResultSet::getSliceStart(int index, int count) const // overlap [0, length) at all if (end > start) { - // right away expand the result array to the size we'll need - result[end - 1] = LLSD(); - for (int i = start; i < end; ++i) + // Right away expand the result array to the size we'll need. + // (end - start) is that size; (end - start - 1) is the index of the + // last entry in result. + result[end - start - 1] = LLSD(); + for (int i = 0; (start + i) < end; ++i) { - result[i] = getSingle(i); + // For this to be a slice, set result[0] = getSingle(start), etc. + result[i] = getSingle(start + i); } } return { result, start }; -- cgit v1.2.3 From f3896d37ca625a4f7060ee5139a8825c2f6e6a74 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Sep 2024 20:39:14 -0400 Subject: Generalize Lua-side result-set machinery for other use cases. Change `result_view()` from a simple function to a callable table so we can add conventional/default functions to it: `result_view.fetch()` is a generic `fetch()` function suitable for use with `result_view()`, and `result_view.close()` is a variadic function that closes result sets for whichever keys are passed. This arises from the fact that any `LL::ResultSet` subclass is accessed generically through its base class, therefore we don't need distinct "getSlice" and "closeResult" operations for different `LLEventAPI` listeners. (It might make sense to relocate those operations to a new generic listener, but for now "LLInventory" works.) That lets `result_view()`'s caller omit the `fetch` parameter unless it requires special behavior. Omitting it uses the generic `result_view.fetch()` function. Moreover, every view returned by `result_view()` now contains a close() function that closes that view's result set. The table returned by LLInventory.lua's `result()` function has a `close()` method; that method can now call `result_view.close()` with the two keys of interest. That table's `__index()` metamethod can now leverage `result_view()`'s default `fetch` function. --- indra/newview/scripts/lua/require/LLInventory.lua | 22 ++---- indra/newview/scripts/lua/require/result_view.lua | 83 +++++++++++++++-------- 2 files changed, 62 insertions(+), 43 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/require/LLInventory.lua b/indra/newview/scripts/lua/require/LLInventory.lua index ce501e75f3..9cf72a8678 100644 --- a/indra/newview/scripts/lua/require/LLInventory.lua +++ b/indra/newview/scripts/lua/require/LLInventory.lua @@ -15,9 +15,7 @@ local function result(keys) -- call result:close() to release result sets before garbage -- collection or script completion close = function(self) - leap.send('LLInventory', - {op='closeResult', - result={self._categories[1], self._items[1]}}) + result_view.close(self._categories[1], self._items[1]) end }, -- The caller of one of our methods that returns a result set @@ -31,17 +29,9 @@ local function result(keys) if not table.find({'categories', 'items'}, field) then return nil end - local view = result_view( - -- We cleverly saved the result set {key, length} pair in - -- a field with the same name but an underscore prefix. - t['_' .. field], - function(key, start) - local fetched = leap.request( - 'LLInventory', - {op='getSlice', result=key, index=start}) - return fetched.slice, fetched.start - end - ) + -- We cleverly saved the result set {key, length} pair in + -- a field with the same name but an underscore prefix. + local view = result_view(t['_' .. field]) -- cache that view for future reference t[field] = view return view @@ -51,8 +41,8 @@ local function result(keys) -- When the table-with-metatable above is destroyed, tell LLInventory -- we're done with its result sets -- whether or not we ever fetched -- either of them. - function(keys) - keys:close() + function(res) + res:close() end ) end diff --git a/indra/newview/scripts/lua/require/result_view.lua b/indra/newview/scripts/lua/require/result_view.lua index c719681c66..5301d7838c 100644 --- a/indra/newview/scripts/lua/require/result_view.lua +++ b/indra/newview/scripts/lua/require/result_view.lua @@ -1,34 +1,36 @@ +local leap = require 'leap' + -- metatable for every result_view() table local mt = { - __len = function(this) - return this.length + __len = function(self) + return self.length end, - __index = function(this, i) + __index = function(self, i) -- right away, convert to 0-relative indexing i -= 1 -- can we find this index within the current slice? - local reli = i - this.start - if 0 <= reli and reli < #this.slice then + local reli = i - self.start + if 0 <= reli and reli < #self.slice then -- Lua 1-relative indexing - return this.slice[reli + 1] + return self.slice[reli + 1] end -- is this index outside the overall result set? - if not (0 <= i and i < this.length) then + if not (0 <= i and i < self.length) then return nil end -- fetch a new slice starting at i, using provided fetch() local start - this.slice, start = this.fetch(this.key, i) + self.slice, start = self.fetch(self.key, i) -- It's possible that caller-provided fetch() function forgot -- to return the adjusted start index of the new slice. In -- Lua, 0 tests as true, so if fetch() returned (slice, 0), - -- we'll duly reset this.start to 0. Otherwise, assume the + -- we'll duly reset self.start to 0. Otherwise, assume the -- requested index was not adjusted: that the returned slice -- really does start at i. - this.start = start or i + self.start = start or i -- Hopefully this slice contains the desired i. -- Back to 1-relative indexing. - return this.slice[i - this.start + 1] + return self.slice[i - self.start + 1] end, -- We purposely avoid putting any array entries (int keys) into -- our table so that access to any int key will always call our @@ -40,7 +42,7 @@ local mt = { -- This result set provides read-only access. -- We do not support pushing updates to individual items back to -- C++; for the intended use cases, that makes no sense. - __newindex = function(this, i, value) + __newindex = function(self, i, value) error("result_view is a read-only data structure", 2) end } @@ -50,20 +52,47 @@ local mt = { -- access to the entire result set. -- key_length: {result set key, total result set length} -- fetch: function(key, start) that returns (slice, adjusted start) -local function result_view(key_length, fetch) - return setmetatable( - { - key=key_length[1], - length=key_length[2], - -- C++ result sets use 0-based indexing, so internally we do too - start=0, - -- start with a dummy array with length 0 - slice={}, - fetch=fetch - }, - -- use our special metatable - mt - ) -end +local result_view = setmetatable( + { + -- generic fetch() function + fetch = function(key, start) + local fetched = leap.request( + 'LLInventory', + {op='getSlice', result=key, index=start}) + return fetched.slice, fetched.start + end, + -- generic close() function accepting variadic result-set keys + close = function(...) + local keys = table.pack(...) + -- table.pack() produces a table with an array entry for every + -- parameter, PLUS an 'n' key with the count. Unfortunately that + -- 'n' key bollixes our conversion to LLSD, which requires either + -- all int keys (for an array) or all string keys (for a map). + keys.n = nil + leap.send('LLInventory', {op='closeResult', result=keys}) + end + }, + { + -- result_view(key_length, fetch) calls this + __call = function(class, key_length, fetch) + return setmetatable( + { + key=key_length[1], + length=key_length[2], + -- C++ result sets use 0-based indexing, so internally we do too + start=0, + -- start with a dummy array with length 0 + slice={}, + -- if caller didn't pass fetch() function, use generic + fetch=fetch or class.fetch, + -- returned view:close() will close result set with passed key + close=function(self) class.close(key_length[1]) end + }, + -- use our special metatable + mt + ) + end + } +) return result_view -- cgit v1.2.3 From a8dd7135f0423384dbbb1e3b98514149c6a69e6b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Sep 2024 20:50:18 -0400 Subject: Use Lua result-set logic for "LLFloaterReg"s "getFloaterNames" op. This is the query that produced so many results that, before we lifted the infinite-loop interrupt limit, inspect(result) hit the limit and terminated. --- indra/llui/llfloaterreglistener.cpp | 20 ++++++++++++++++---- indra/newview/scripts/lua/require/UI.lua | 8 +++++++- 2 files changed, 23 insertions(+), 5 deletions(-) (limited to 'indra') diff --git a/indra/llui/llfloaterreglistener.cpp b/indra/llui/llfloaterreglistener.cpp index e17f9f4dd6..bd8d87086e 100644 --- a/indra/llui/llfloaterreglistener.cpp +++ b/indra/llui/llfloaterreglistener.cpp @@ -38,6 +38,7 @@ #include "llfloater.h" #include "llbutton.h" #include "llluafloater.h" +#include "resultset.h" LLFloaterRegListener::LLFloaterRegListener(): LLEventAPI("LLFloaterReg", @@ -82,9 +83,9 @@ LLFloaterRegListener::LLFloaterRegListener(): &LLFloaterRegListener::getLuaFloaterEvents); add("getFloaterNames", - "Return the table of all registered floaters", + "Return result set key [\"floaters\"] for names of all registered floaters", &LLFloaterRegListener::getFloaterNames, - llsd::map("reply", LLSD())); + llsd::map("reply", LLSD::String())); } void LLFloaterRegListener::getBuildMap(const LLSD& event) const @@ -126,10 +127,22 @@ void LLFloaterRegListener::instanceVisible(const LLSD& event) const event); } +struct NameResultSet: public LL::ResultSet +{ + NameResultSet(): + LL::ResultSet("floaters"), + mNames(LLFloaterReg::getFloaterNames()) + {} + LLSD mNames; + + int getLength() const override { return narrow(mNames.size()); } + LLSD getSingle(int index) const override { return mNames[index]; } +}; void LLFloaterRegListener::getFloaterNames(const LLSD &event) const { - Response response(llsd::map("floaters", LLFloaterReg::getFloaterNames()), event); + auto nameresult = new NameResultSet; + sendReply(llsd::map("floaters", nameresult->getKeyLength()), event); } void LLFloaterRegListener::clickButton(const LLSD& event) const @@ -178,4 +191,3 @@ void LLFloaterRegListener::getLuaFloaterEvents(const LLSD &event) const { Response response(llsd::map("events", LLLuaFloater::getEventsData()), event); } - diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua index bbcae3514a..aa64c0c7f9 100644 --- a/indra/newview/scripts/lua/require/UI.lua +++ b/indra/newview/scripts/lua/require/UI.lua @@ -2,6 +2,7 @@ local leap = require 'leap' local mapargs = require 'mapargs' +local result_view = require 'result_view' local Timer = (require 'timers').Timer local util = require 'util' @@ -234,7 +235,12 @@ function UI.closeAllFloaters() end function UI.getFloaterNames() - return leap.request("LLFloaterReg", {op = "getFloaterNames"}).floaters + local key_length = leap.request("LLFloaterReg", {op = "getFloaterNames"}).floaters + local view = result_view(key_length) + return LL.setdtor( + 'registered floater names', + view, + function(self) view:close() end) end return UI -- cgit v1.2.3 From 35c3f0227c334e059abdc36c36cc942a517d92ec Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 4 Sep 2024 07:43:48 -0400 Subject: Instead of traversing all calling cards, pick a selected few. Make test_LLInventory.lua directly select from the calling_cards result set, instead of first copying all names to a separate array. --- indra/newview/scripts/lua/test_LLInventory.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/test_LLInventory.lua b/indra/newview/scripts/lua/test_LLInventory.lua index 918ca56a2e..de57484bcd 100644 --- a/indra/newview/scripts/lua/test_LLInventory.lua +++ b/indra/newview/scripts/lua/test_LLInventory.lua @@ -15,9 +15,10 @@ calling_cards_id = LLInventory.getBasicFolderID('callcard') calling_cards = LLInventory.getDirectDescendents(calling_cards_id).items -- Print a random calling card name from 'Calling Cards' folder -local card_names = {} -for _, value in pairs(calling_cards) do - table.insert(card_names, value.name) -end +-- (because getDirectDescendents().items is a Lua result set, selecting +-- a random entry only fetches one slice containing that entry) math.randomseed(os.time()) -print("Random calling card: " .. inspect(card_names[math.random(#card_names)])) +for i = 1, 5 do + pick = math.random(#calling_cards) + print(`Random calling card (#{pick} of {#calling_cards}): {calling_cards[pick].name}`) +end -- cgit v1.2.3 From d67ad5da3b5a37f7b4cb78e686ae36f31c513153 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 4 Sep 2024 09:15:10 -0400 Subject: `result_view()`'s table's `close()` method need not be further wrapped. `LL.setdtor(desc, table, func)` eventually calls `func(table)`. So the `close()` method on the table returned by `result_view()` can be directly passed to `setdtor()`, instead of wrapped in a new anonymous function whose only job is to pass the table to it. Moreover, there's no need for the table returned by LLInventory.lua's `result()` function to lazily instantiate the `result_view()` for `categories` or `items`: neither `result_view` will fetch a slice unless asked. Just return `{categories=result_view(...), items=result_view(...), close=...}`. This dramatically simplifies the `result()` function. Since that table also defines a `close()` function, that too can be passed directly to `setdtor()` without being wrapped in a new anonymous function. --- indra/newview/scripts/lua/require/LLInventory.lua | 52 +++++------------------ indra/newview/scripts/lua/require/UI.lua | 5 +-- 2 files changed, 12 insertions(+), 45 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/require/LLInventory.lua b/indra/newview/scripts/lua/require/LLInventory.lua index 9cf72a8678..2c80a8602b 100644 --- a/indra/newview/scripts/lua/require/LLInventory.lua +++ b/indra/newview/scripts/lua/require/LLInventory.lua @@ -3,48 +3,18 @@ local mapargs = require 'mapargs' local result_view = require 'result_view' local function result(keys) - return LL.setdtor( - 'LLInventory result', - setmetatable( - -- the basic table wrapped by setmetatable just captures the int - -- result-set {key, length} pairs from 'keys', but with underscore - -- prefixes - { - _categories=keys.categories, - _items=keys.items, - -- call result:close() to release result sets before garbage - -- collection or script completion - close = function(self) - result_view.close(self._categories[1], self._items[1]) - end - }, - -- The caller of one of our methods that returns a result set - -- isn't necessarily interested in both categories and items, so - -- don't proactively populate both. Instead, when caller references - -- either 'categories' or 'items', the __index() metamethod - -- populates that field. - { - __index = function(t, field) - -- we really don't care about references to any other field - if not table.find({'categories', 'items'}, field) then - return nil - end - -- We cleverly saved the result set {key, length} pair in - -- a field with the same name but an underscore prefix. - local view = result_view(t['_' .. field]) - -- cache that view for future reference - t[field] = view - return view - end - } - ), - -- When the table-with-metatable above is destroyed, tell LLInventory - -- we're done with its result sets -- whether or not we ever fetched - -- either of them. - function(res) - res:close() + -- capture result_view() instances for both categories and items + local result_table = { + categories=result_view(keys.categories), + items=result_view(keys.items), + -- call result_table:close() to release result sets before garbage + -- collection or script completion + close = function(self) + result_view.close(keys.categories[1], keys.items[1]) end - ) + } + -- When the result_table is destroyed, close its result_views. + return LL.setdtor('LLInventory result', result_table, result_table.close) end local LLInventory = {} diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua index aa64c0c7f9..73a76fa6b8 100644 --- a/indra/newview/scripts/lua/require/UI.lua +++ b/indra/newview/scripts/lua/require/UI.lua @@ -237,10 +237,7 @@ end function UI.getFloaterNames() local key_length = leap.request("LLFloaterReg", {op = "getFloaterNames"}).floaters local view = result_view(key_length) - return LL.setdtor( - 'registered floater names', - view, - function(self) view:close() end) + return LL.setdtor('registered floater names', view, view.close) end return UI -- cgit v1.2.3 From a6b85244a6f943a4598ff9b7b8a3343eb1e0d11e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 4 Sep 2024 10:26:40 -0400 Subject: Fix test: new traceback info changed error message. --- indra/newview/tests/llluamanager_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index 3209d93d39..c2abd27f96 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -463,8 +463,8 @@ namespace tut // but now we have to give the startScriptLine() coroutine a chance to run auto [count, result] = future.get(); ensure_equals("killed Lua script terminated normally", count, -1); - ensure_equals("unexpected killed Lua script error", - result.asString(), "viewer is stopping"); + ensure_contains("unexpected killed Lua script error", + result.asString(), "viewer is stopping"); } template<> template<> -- cgit v1.2.3 From ea24ac899ca28a587be0e187b77873a25d08a556 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 4 Sep 2024 14:07:26 -0400 Subject: Extract coroutine-aware synchronization primitives to new header. Changes on new main and changes on Lua project branch combined into a header circularity. Resolved by hoisting coroutine-aware synchronization primitives out to a new llcoromutex.h file in the `llcoro` namespace, rather than being literally members of the `LLCoros` class. But retained `using` declarations in `LLCoros` for backwards compatibility. --- indra/llcommon/CMakeLists.txt | 1 + indra/llcommon/llcoromutex.h | 56 ++++++++++++++++++++++++++++++++ indra/llcommon/llcoros.h | 34 +++++-------------- indra/llcommon/llevents.cpp | 10 +++--- indra/llcommon/llevents.h | 4 +-- indra/llplugin/llpluginprocessparent.cpp | 16 ++++----- indra/llplugin/llpluginprocessparent.h | 3 +- 7 files changed, 83 insertions(+), 41 deletions(-) create mode 100644 indra/llcommon/llcoromutex.h (limited to 'indra') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index f893702118..22cc22abba 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -153,6 +153,7 @@ set(llcommon_HEADER_FILES llcommonutils.h llcond.h llcoros.h + llcoromutex.h llcrc.h llcriticaldamp.h lldate.h diff --git a/indra/llcommon/llcoromutex.h b/indra/llcommon/llcoromutex.h new file mode 100644 index 0000000000..e740e494c2 --- /dev/null +++ b/indra/llcommon/llcoromutex.h @@ -0,0 +1,56 @@ +/** + * @file llcoromutex.h + * @author Nat Goodspeed + * @date 2024-09-04 + * @brief Coroutine-aware synchronization primitives + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLCOROMUTEX_H) +#define LL_LLCOROMUTEX_H + +#include "mutex.h" +#include +#include + +// e.g. #include LLCOROS_MUTEX_HEADER +#define LLCOROS_MUTEX_HEADER +#define LLCOROS_CONDVAR_HEADER + +namespace boost { + namespace fibers { + class mutex; + enum class cv_status; + class condition_variable; + } +} + +namespace llcoro +{ + +/** + * Aliases for promise and future. An older underlying future implementation + * required us to wrap future; that's no longer needed. However -- if it's + * important to restore kill() functionality, we might need to provide a + * proxy, so continue using the aliases. + */ +template +using Promise = boost::fibers::promise; +template +using Future = boost::fibers::future; +template +inline +static Future getFuture(Promise& promise) { return promise.get_future(); } + +// use mutex, lock, condition_variable suitable for coroutines +using Mutex = boost::fibers::mutex; +using LockType = std::unique_lock; +using cv_status = boost::fibers::cv_status; +using ConditionVariable = boost::fibers::condition_variable; + +} // namespace llcoro + +#endif /* ! defined(LL_LLCOROMUTEX_H) */ diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h index f26ed882e8..b7ca1af109 100644 --- a/indra/llcommon/llcoros.h +++ b/indra/llcommon/llcoros.h @@ -29,31 +29,17 @@ #if ! defined(LL_LLCOROS_H) #define LL_LLCOROS_H +#include "llcoromutex.h" #include "llevents.h" #include "llexception.h" #include "llinstancetracker.h" #include "llsingleton.h" -#include "mutex.h" #include -#include -#include #include #include #include #include -// e.g. #include LLCOROS_MUTEX_HEADER -#define LLCOROS_MUTEX_HEADER -#define LLCOROS_CONDVAR_HEADER - -namespace boost { - namespace fibers { - class mutex; - enum class cv_status; - class condition_variable; - } -} - /** * Registry of named Boost.Coroutine instances * @@ -322,23 +308,21 @@ public: LLVoidListener cleanup); /** - * Aliases for promise and future. An older underlying future implementation - * required us to wrap future; that's no longer needed. However -- if it's - * important to restore kill() functionality, we might need to provide a - * proxy, so continue using the aliases. + * LLCoros aliases for promise and future, for backwards compatibility. + * These have been hoisted out to the llcoro namespace. */ template - using Promise = boost::fibers::promise; + using Promise = llcoro::Promise; template - using Future = boost::fibers::future; + using Future = llcoro::Future; template static Future getFuture(Promise& promise) { return promise.get_future(); } // use mutex, lock, condition_variable suitable for coroutines - using Mutex = boost::fibers::mutex; - using LockType = std::unique_lock; - using cv_status = boost::fibers::cv_status; - using ConditionVariable = boost::fibers::condition_variable; + using Mutex = llcoro::Mutex; + using LockType = llcoro::LockType; + using cv_status = llcoro::cv_status; + using ConditionVariable = llcoro::ConditionVariable; /// for data local to each running coroutine template diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index a694f0dc7f..6531b951b9 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -389,7 +389,7 @@ std::string LLEventPump::inventName(const std::string& pfx) void LLEventPump::clear() { - LLCoros::LockType lock(mConnectionListMutex); + llcoro::LockType lock(mConnectionListMutex); // Destroy the original LLStandardSignal instance, replacing it with a // whole new one. mSignal = std::make_shared(); @@ -401,7 +401,7 @@ void LLEventPump::reset() { // Resetting mSignal is supposed to disconnect everything on its own // But due to crash on 'reset' added explicit cleanup to get more data - LLCoros::LockType lock(mConnectionListMutex); + llcoro::LockType lock(mConnectionListMutex); ConnectionMap::const_iterator iter = mConnections.begin(); ConnectionMap::const_iterator end = mConnections.end(); while (iter!=end) @@ -426,7 +426,7 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLAwareL return LLBoundListener(); } - LLCoros::LockType lock(mConnectionListMutex); + llcoro::LockType lock(mConnectionListMutex); float nodePosition = 1.0; @@ -589,7 +589,7 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLAwareL LLBoundListener LLEventPump::getListener(const std::string& name) { - LLCoros::LockType lock(mConnectionListMutex); + llcoro::LockType lock(mConnectionListMutex); ConnectionMap::const_iterator found = mConnections.find(name); if (found != mConnections.end()) { @@ -601,7 +601,7 @@ LLBoundListener LLEventPump::getListener(const std::string& name) void LLEventPump::stopListening(const std::string& name) { - LLCoros::LockType lock(mConnectionListMutex); + llcoro::LockType lock(mConnectionListMutex); ConnectionMap::iterator found = mConnections.find(name); if (found != mConnections.end()) { diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 368138a50b..f92257238d 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -50,7 +50,7 @@ #endif #include -#include "llcoros.h" +#include "llcoromutex.h" #include "lldependencies.h" #include "llexception.h" #include "llsd.h" @@ -557,7 +557,7 @@ private: private: std::string mName; - LLCoros::Mutex mConnectionListMutex; + llcoro::Mutex mConnectionListMutex; protected: template diff --git a/indra/llplugin/llpluginprocessparent.cpp b/indra/llplugin/llpluginprocessparent.cpp index 00abcf740f..2035194d69 100644 --- a/indra/llplugin/llpluginprocessparent.cpp +++ b/indra/llplugin/llpluginprocessparent.cpp @@ -45,7 +45,7 @@ LLPluginProcessParentOwner::~LLPluginProcessParentOwner() bool LLPluginProcessParent::sUseReadThread = false; apr_pollset_t *LLPluginProcessParent::sPollSet = NULL; bool LLPluginProcessParent::sPollsetNeedsRebuild = false; -LLCoros::Mutex *LLPluginProcessParent::sInstancesMutex; +llcoro::Mutex *LLPluginProcessParent::sInstancesMutex; LLPluginProcessParent::mapInstances_t LLPluginProcessParent::sInstances; LLThread *LLPluginProcessParent::sReadThread = NULL; @@ -105,7 +105,7 @@ LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner): { if(!sInstancesMutex) { - sInstancesMutex = new LLCoros::Mutex(); + sInstancesMutex = new llcoro::Mutex(); } mOwner = owner; @@ -175,7 +175,7 @@ LLPluginProcessParent::ptr_t LLPluginProcessParent::create(LLPluginProcessParent // Don't add to the global list until fully constructed. { - LLCoros::LockType lock(*sInstancesMutex); + llcoro::LockType lock(*sInstancesMutex); sInstances.insert(mapInstances_t::value_type(that.get(), that)); } @@ -185,7 +185,7 @@ LLPluginProcessParent::ptr_t LLPluginProcessParent::create(LLPluginProcessParent /*static*/ void LLPluginProcessParent::shutdown() { - LLCoros::LockType lock(*sInstancesMutex); + llcoro::LockType lock(*sInstancesMutex); mapInstances_t::iterator it; for (it = sInstances.begin(); it != sInstances.end(); ++it) @@ -243,7 +243,7 @@ bool LLPluginProcessParent::pollTick() { // this grabs a copy of the smart pointer to ourselves to ensure that we do not // get destroyed until after this method returns. - LLCoros::LockType lock(*sInstancesMutex); + llcoro::LockType lock(*sInstancesMutex); mapInstances_t::iterator it = sInstances.find(this); if (it != sInstances.end()) that = (*it).second; @@ -262,7 +262,7 @@ void LLPluginProcessParent::removeFromProcessing() // Remove from the global list before beginning destruction. { // Make sure to get the global mutex _first_ here, to avoid a possible deadlock against LLPluginProcessParent::poll() - LLCoros::LockType lock(*sInstancesMutex); + llcoro::LockType lock(*sInstancesMutex); { LLMutexLock lock2(&mIncomingQueueMutex); sInstances.erase(this); @@ -844,7 +844,7 @@ void LLPluginProcessParent::updatePollset() return; } - LLCoros::LockType lock(*sInstancesMutex); + llcoro::LockType lock(*sInstancesMutex); if(sPollSet) { @@ -967,7 +967,7 @@ void LLPluginProcessParent::poll(F64 timeout) mapInstances_t::iterator it; { - LLCoros::LockType lock(*sInstancesMutex); + llcoro::LockType lock(*sInstancesMutex); it = sInstances.find(thatId); if (it != sInstances.end()) that = (*it).second; diff --git a/indra/llplugin/llpluginprocessparent.h b/indra/llplugin/llpluginprocessparent.h index d1c4933d81..903be35526 100644 --- a/indra/llplugin/llpluginprocessparent.h +++ b/indra/llplugin/llpluginprocessparent.h @@ -33,6 +33,7 @@ #include #include "llapr.h" +#include "llcoromutex.h" #include "llprocess.h" #include "llpluginmessage.h" #include "llpluginmessagepipe.h" @@ -207,7 +208,7 @@ private: apr_pollfd_t mPollFD; static apr_pollset_t *sPollSet; static bool sPollsetNeedsRebuild; - static LLCoros::Mutex *sInstancesMutex; + static llcoro::Mutex *sInstancesMutex; static mapInstances_t sInstances; static void dirtyPollSet(); static void updatePollset(); -- cgit v1.2.3 From c4f29a535359fe1ecdb8bec45596f40b02891cd1 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 4 Sep 2024 14:17:32 -0400 Subject: Resolve a few unresolved merge conflicts. --- indra/llui/llmenugl.h | 10 +++------- indra/llxml/tests/llcontrol_test.cpp | 10 +--------- indra/newview/llmediadataclient.h | 6 ------ indra/newview/llpanelpeople.cpp | 12 ------------ 4 files changed, 4 insertions(+), 34 deletions(-) (limited to 'indra') diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index 65211bf122..da5d7874c6 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -562,6 +562,8 @@ public: // add a context menu branch bool appendContextSubMenu(LLMenuGL *menu); + void createSpilloverBranch(); + void cleanupSpilloverBranch(); // Add the menu item to this menu. virtual bool append( LLMenuItemGL* item ); @@ -806,15 +808,9 @@ public: void resetMenuTrigger() { mAltKeyTrigger = false; } // add a menu - this will create a drop down menu. -<<<<<<< variant A - virtual BOOL appendMenu(LLMenuGL *menu); + virtual bool appendMenu(LLMenuGL *menu); private: ->>>>>>> variant B - virtual bool appendMenu( LLMenuGL* menu ); -####### Ancestor - virtual BOOL appendMenu( LLMenuGL* menu ); -======= end // rearrange the child rects so they fit the shape of the menu // bar. virtual void arrange( void ); diff --git a/indra/llxml/tests/llcontrol_test.cpp b/indra/llxml/tests/llcontrol_test.cpp index 4cb66a91fb..52c202bceb 100644 --- a/indra/llxml/tests/llcontrol_test.cpp +++ b/indra/llxml/tests/llcontrol_test.cpp @@ -134,16 +134,8 @@ namespace tut LLControlGroup test_cg("foo3"); std::string temp_test_file = (mTestConfigDir + "setting_llsd_persist_temp.xml"); mCleanups.push_back(temp_test_file); -<<<<<<< variant A - mCG->saveToFile(temp_test_file.c_str(), TRUE); - results = test_cg.loadFromFile(temp_test_file); ->>>>>>> variant B mCG->saveToFile(temp_test_file.c_str(), true); - results = test_cg.loadFromFile(temp_test_file.c_str()); -####### Ancestor - mCG->saveToFile(temp_test_file.c_str(), TRUE); - results = test_cg.loadFromFile(temp_test_file.c_str()); -======= end + results = test_cg.loadFromFile(temp_test_file); //If we haven't changed any settings, then we shouldn't have any settings to load ensure("number of non-persisted changed settings loaded", (results == 0)); } diff --git a/indra/newview/llmediadataclient.h b/indra/newview/llmediadataclient.h index 736f4d8537..a5e6e43e3f 100644 --- a/indra/newview/llmediadataclient.h +++ b/indra/newview/llmediadataclient.h @@ -286,13 +286,7 @@ private: { public: QueueTimer(F32 time, LLMediaDataClient *mdc); -<<<<<<< variant A bool tick() override; ->>>>>>> variant B - virtual bool tick(); -####### Ancestor - virtual BOOL tick(); -======= end private: // back-pointer LLPointer mMDC; diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp index ea4a5bfdf1..fb96718d1c 100644 --- a/indra/newview/llpanelpeople.cpp +++ b/indra/newview/llpanelpeople.cpp @@ -364,13 +364,7 @@ public: } -<<<<<<< variant A bool tick() override ->>>>>>> variant B - /*virtual*/ bool tick() -####### Ancestor - /*virtual*/ BOOL tick() -======= end { if (!mIsActive) return false; @@ -510,13 +504,7 @@ public: } } -<<<<<<< variant A bool tick() override ->>>>>>> variant B - /*virtual*/ bool tick() -####### Ancestor - /*virtual*/ BOOL tick() -======= end { update(); return false; -- cgit v1.2.3 From d5fa10180344d419d5d9a9fe580044f28aa64e43 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 4 Sep 2024 14:19:29 -0400 Subject: Adapt new code from main branch to Lua project calling convention. --- indra/newview/llpanelnearbymedia.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/newview/llpanelnearbymedia.cpp b/indra/newview/llpanelnearbymedia.cpp index de59feab8e..86a88d6793 100644 --- a/indra/newview/llpanelnearbymedia.cpp +++ b/indra/newview/llpanelnearbymedia.cpp @@ -103,10 +103,10 @@ LLPanelNearByMedia::LLPanelNearByMedia() // Context menu handler. mCommitCallbackRegistrar.add("SelectedMediaCtrl.Action", - [this](LLUICtrl* ctrl, const LLSD& data) + {[this](LLUICtrl* ctrl, const LLSD& data) { onMenuAction(data); - }); + }}); mEnableCallbackRegistrar.add("SelectedMediaCtrl.Visible", [this](LLUICtrl* ctrl, const LLSD& data) { -- cgit v1.2.3 From a980a8f30870d35a5a45cbf52169f7bf35cf95ce Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 4 Sep 2024 14:23:31 -0400 Subject: Swat a few more buzzing BOOLs. --- indra/llui/llluafloater.cpp | 2 +- indra/llui/llluafloater.h | 2 +- indra/newview/llfloaterluadebug.cpp | 2 +- indra/newview/llfloaterluadebug.h | 2 +- indra/newview/llfloaterluascripts.cpp | 2 +- indra/newview/llfloaterluascripts.h | 2 +- indra/newview/lltoolplacer.cpp | 2 +- indra/newview/lltoolplacer.h | 4 ++-- 8 files changed, 9 insertions(+), 9 deletions(-) (limited to 'indra') diff --git a/indra/llui/llluafloater.cpp b/indra/llui/llluafloater.cpp index ca80a2c451..ccdadc6ae0 100644 --- a/indra/llui/llluafloater.cpp +++ b/indra/llui/llluafloater.cpp @@ -157,7 +157,7 @@ LLLuaFloater::~LLLuaFloater() post(LLSD()); } -BOOL LLLuaFloater::postBuild() +bool LLLuaFloater::postBuild() { for (LLView *view : *getChildList()) { diff --git a/indra/llui/llluafloater.h b/indra/llui/llluafloater.h index d0641d41b0..41132f926d 100644 --- a/indra/llui/llluafloater.h +++ b/indra/llui/llluafloater.h @@ -34,7 +34,7 @@ class LLLuaFloater : public LLFloater { public: LLLuaFloater(const LLSD &key); - BOOL postBuild(); + bool postBuild(); virtual ~LLLuaFloater(); void registerCallback(const std::string &ctrl_name, const std::string &event); diff --git a/indra/newview/llfloaterluadebug.cpp b/indra/newview/llfloaterluadebug.cpp index ef24481464..2d0f8949e5 100644 --- a/indra/newview/llfloaterluadebug.cpp +++ b/indra/newview/llfloaterluadebug.cpp @@ -49,7 +49,7 @@ LLFloaterLUADebug::LLFloaterLUADebug(const LLSD &key) } -BOOL LLFloaterLUADebug::postBuild() +bool LLFloaterLUADebug::postBuild() { mResultOutput = getChild("result_text"); mLineInput = getChild("lua_cmd"); diff --git a/indra/newview/llfloaterluadebug.h b/indra/newview/llfloaterluadebug.h index 9f044df9d9..e27c63b74d 100644 --- a/indra/newview/llfloaterluadebug.h +++ b/indra/newview/llfloaterluadebug.h @@ -48,7 +48,7 @@ class LLFloaterLUADebug : LLFloaterLUADebug(const LLSD& key); virtual ~LLFloaterLUADebug(); - /*virtual*/ BOOL postBuild(); + /*virtual*/ bool postBuild(); void onExecuteClicked(); void onBtnBrowse(); diff --git a/indra/newview/llfloaterluascripts.cpp b/indra/newview/llfloaterluascripts.cpp index 60cdadfbee..79623cdefb 100644 --- a/indra/newview/llfloaterluascripts.cpp +++ b/indra/newview/llfloaterluascripts.cpp @@ -60,7 +60,7 @@ LLFloaterLUAScripts::LLFloaterLUAScripts(const LLSD &key) } -BOOL LLFloaterLUAScripts::postBuild() +bool LLFloaterLUAScripts::postBuild() { mScriptList = getChild("scripts_list"); mScriptList->setRightMouseDownCallback([this](LLUICtrl *ctrl, S32 x, S32 y, MASK mask) { onScrollListRightClicked(ctrl, x, y);}); diff --git a/indra/newview/llfloaterluascripts.h b/indra/newview/llfloaterluascripts.h index 24d3f00a80..14ca42d6fb 100644 --- a/indra/newview/llfloaterluascripts.h +++ b/indra/newview/llfloaterluascripts.h @@ -37,7 +37,7 @@ class LLFloaterLUAScripts : LLFloaterLUAScripts(const LLSD &key); virtual ~LLFloaterLUAScripts(); - BOOL postBuild(); + bool postBuild(); void draw(); private: diff --git a/indra/newview/lltoolplacer.cpp b/indra/newview/lltoolplacer.cpp index 63846bf5b7..2ac5d6b6b1 100644 --- a/indra/newview/lltoolplacer.cpp +++ b/indra/newview/lltoolplacer.cpp @@ -172,7 +172,7 @@ bool LLToolPlacer::addObject( LLPCode pcode, S32 x, S32 y, U8 use_physics ) return rezNewObject(pcode,hit_obj, hit_face, b_hit_land, ray_start_region, ray_end_region, regionp, use_physics); } -BOOL LLToolPlacer::rezNewObject(LLPCode pcode, LLViewerObject * hit_obj, S32 hit_face, BOOL b_hit_land, LLVector3 ray_start_region, +bool LLToolPlacer::rezNewObject(LLPCode pcode, LLViewerObject * hit_obj, S32 hit_face, bool b_hit_land, LLVector3 ray_start_region, LLVector3 ray_end_region, LLViewerRegion* regionp, U8 use_physics) { if( hit_obj && (hit_obj->isAvatar() || hit_obj->isAttachment()) ) diff --git a/indra/newview/lltoolplacer.h b/indra/newview/lltoolplacer.h index 421a2f25f7..92446c319b 100644 --- a/indra/newview/lltoolplacer.h +++ b/indra/newview/lltoolplacer.h @@ -50,8 +50,8 @@ public: static void setObjectType( LLPCode type ) { sObjectType = type; } static LLPCode getObjectType() { return sObjectType; } - static BOOL addObject(LLPCode pcode, S32 x, S32 y, U8 use_physics); - static BOOL rezNewObject(LLPCode pcode, LLViewerObject* hit_obj, S32 hit_face, BOOL b_hit_land, LLVector3 ray_start_region, +// static bool addObject(LLPCode pcode, S32 x, S32 y, U8 use_physics); + static bool rezNewObject(LLPCode pcode, LLViewerObject* hit_obj, S32 hit_face, bool b_hit_land, LLVector3 ray_start_region, LLVector3 ray_end_region, LLViewerRegion *regionp, U8 use_physics); protected: -- cgit v1.2.3 From 5319d314206c7c1c21b2bbd3a661c0c520373dcc Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 4 Sep 2024 16:24:42 -0400 Subject: Windows build fixes --- indra/llcommon/llcoromutex.h | 2 +- indra/llcommon/lua_function.cpp | 4 ++-- indra/newview/llfloatersettingsdebug.cpp | 2 +- indra/newview/tests/llluamanager_test.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llcoromutex.h b/indra/llcommon/llcoromutex.h index e740e494c2..3405e79478 100644 --- a/indra/llcommon/llcoromutex.h +++ b/indra/llcommon/llcoromutex.h @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2024-09-04 * @brief Coroutine-aware synchronization primitives - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index ffb90032d2..c0090dd395 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -418,7 +418,7 @@ void lua_pushllsd(lua_State* L, const LLSD& data) case LLSD::TypeMap: { // push a new table with space for our non-array keys - lua_createtable(L, 0, data.size()); + lua_createtable(L, 0, narrow(data.size())); for (const auto& pair: llsd::inMap(data)) { // push value -- so now table is at -2, value at -1 @@ -432,7 +432,7 @@ void lua_pushllsd(lua_State* L, const LLSD& data) case LLSD::TypeArray: { // push a new table with space for array entries - lua_createtable(L, data.size(), 0); + lua_createtable(L, narrow(data.size()), 0); lua_Integer key{ 0 }; for (const auto& item: llsd::inArray(data)) { diff --git a/indra/newview/llfloatersettingsdebug.cpp b/indra/newview/llfloatersettingsdebug.cpp index 9c87e6fb3b..63ae1e60e8 100644 --- a/indra/newview/llfloatersettingsdebug.cpp +++ b/indra/newview/llfloatersettingsdebug.cpp @@ -660,5 +660,5 @@ void LLFloaterSettingsDebug::hideUIControls() void LLFloaterSettingsDebug::onClickCopy() { std::string setting_name = mSettingName->getText(); - LLClipboard::instance().copyToClipboard(utf8str_to_wstring(setting_name), 0, setting_name.size()); + LLClipboard::instance().copyToClipboard(utf8str_to_wstring(setting_name), 0, narrow(setting_name.size())); } diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index 4b143b52db..8ce5c357e0 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -287,7 +287,7 @@ namespace tut while (expect_array.size() > 0 && send_array[expect_array.size() - 1].isUndefined()) { - expect_array.erase(expect_array.size() - 1); + expect_array.erase(LLSD::Integer(expect_array.size() - 1)); } round_trip("array", send_array, expect_array); -- cgit v1.2.3 From 6a747e1ce027700a3609f4c377179bfa29c3ce31 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 4 Sep 2024 16:29:09 -0400 Subject: Insidious trailing whitespace --- indra/newview/llagentlistener.cpp | 2 +- indra/newview/llagentlistener.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/newview/llagentlistener.cpp b/indra/newview/llagentlistener.cpp index 2ff4adcf5e..10810b569f 100644 --- a/indra/newview/llagentlistener.cpp +++ b/indra/newview/llagentlistener.cpp @@ -162,7 +162,7 @@ LLAgentListener::LLAgentListener(LLAgent &agent) add("removeCameraParams", "Reset Follow camera params", &LLAgentListener::removeFollowCamParams); - + add("playAnimation", "Play [\"item_id\"] animation locally (by default) or [\"inworld\"] (when set to true)", &LLAgentListener::playAnimation, diff --git a/indra/newview/llagentlistener.h b/indra/newview/llagentlistener.h index c77d1b3fc9..2765bb5b66 100644 --- a/indra/newview/llagentlistener.h +++ b/indra/newview/llagentlistener.h @@ -61,7 +61,7 @@ private: void setFollowCamParams(LLSD const & event_data) const; void setFollowCamActive(LLSD const & event_data) const; void removeFollowCamParams(LLSD const & event_data) const; - + void playAnimation(LLSD const &event_data); void playAnimation_(const LLUUID& asset_id, const bool inworld); void stopAnimation(LLSD const &event_data); -- cgit v1.2.3 From 49bf86b52459b183d3988388dbb74d8888a71925 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 5 Sep 2024 08:45:24 -0400 Subject: Fix a few trailing whitespaces. --- indra/llcommon/llinttracker.h | 2 +- indra/llcommon/resultset.cpp | 2 +- indra/llcommon/resultset.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llinttracker.h b/indra/llcommon/llinttracker.h index 86c30bc7aa..fd6d24d0fd 100644 --- a/indra/llcommon/llinttracker.h +++ b/indra/llcommon/llinttracker.h @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2024-08-30 * @brief LLIntTracker isa LLInstanceTracker with generated int keys. - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/resultset.cpp b/indra/llcommon/resultset.cpp index cb46db04fa..4d7b00eabd 100644 --- a/indra/llcommon/resultset.cpp +++ b/indra/llcommon/resultset.cpp @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2024-09-03 * @brief Implementation for resultset. - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/resultset.h b/indra/llcommon/resultset.h index 9b9ecbb21e..90d52b6fe4 100644 --- a/indra/llcommon/resultset.h +++ b/indra/llcommon/resultset.h @@ -4,7 +4,7 @@ * @date 2024-09-03 * @brief ResultSet is an abstract base class to allow scripted access to * potentially large collections representable as LLSD arrays. - * + * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ -- cgit v1.2.3 From 866f900a3be8bbbf8fb10e6ccb87bb80882b4a6e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 5 Sep 2024 11:08:26 -0400 Subject: Fix a merge glitch in llcoros.h. --- indra/llcommon/llcoros.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h index 2272237fa3..0291d7f1d9 100644 --- a/indra/llcommon/llcoros.h +++ b/indra/llcommon/llcoros.h @@ -324,7 +324,7 @@ public: // LockType is deprecated; see llcoromutex.h using LockType = llcoro::LockType; using cv_status = llcoro::cv_status; - using ConditionVariable = llcoro::condition_variable; + using ConditionVariable = llcoro::ConditionVariable; /// for data local to each running coroutine template -- cgit v1.2.3 From 9830f56b1b9d6afc6adfcc3ee3fbd540da9f1737 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 5 Sep 2024 11:10:17 -0400 Subject: In llcoromutex.h, pull in llcoro::RMutex from develop branch. Also add develop branch's comments about llcoro::LockType being deprecated. --- indra/llcommon/llcoromutex.h | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'indra') diff --git a/indra/llcommon/llcoromutex.h b/indra/llcommon/llcoromutex.h index 3405e79478..c0ceac4b22 100644 --- a/indra/llcommon/llcoromutex.h +++ b/indra/llcommon/llcoromutex.h @@ -18,11 +18,13 @@ // e.g. #include LLCOROS_MUTEX_HEADER #define LLCOROS_MUTEX_HEADER +#define LLCOROS_RMUTEX_HEADER #define LLCOROS_CONDVAR_HEADER namespace boost { namespace fibers { class mutex; + class recursive_mutex; enum class cv_status; class condition_variable; } @@ -47,6 +49,12 @@ static Future getFuture(Promise& promise) { return promise.get_future(); } // use mutex, lock, condition_variable suitable for coroutines using Mutex = boost::fibers::mutex; +using RMutex = boost::fibers::recursive_mutex; +// With C++17, LockType is deprecated: at this point we can directly +// declare 'std::unique_lock lk(some_mutex)' without explicitly stating +// the mutex type. Sadly, making LockType an alias template for +// std::unique_lock doesn't work the same way: Class Template Argument +// Deduction only works for class templates, not alias templates. using LockType = std::unique_lock; using cv_status = boost::fibers::cv_status; using ConditionVariable = boost::fibers::condition_variable; -- cgit v1.2.3 From 953f7c9c1da4b83cabbf91f281445c3704a2f229 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 5 Sep 2024 12:33:47 -0400 Subject: Fix build errors from merging develop into release/luau-scripting. --- indra/llcommon/llerror.cpp | 2 ++ indra/newview/llfloatergltfasseteditor.cpp | 4 ++-- indra/newview/llfloatersettingscolor.cpp | 4 ++-- indra/newview/llfloatersettingsdebug.cpp | 3 +-- indra/newview/lluilistener.cpp | 5 +++-- 5 files changed, 10 insertions(+), 8 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 8705758d3f..e6fe1eca75 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -64,6 +64,8 @@ #define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED #include +#include LLCOROS_RMUTEX_HEADER + namespace { #if LL_WINDOWS void debugger_print(const std::string& s) diff --git a/indra/newview/llfloatergltfasseteditor.cpp b/indra/newview/llfloatergltfasseteditor.cpp index d2cf24f1dd..f28c01d713 100644 --- a/indra/newview/llfloatergltfasseteditor.cpp +++ b/indra/newview/llfloatergltfasseteditor.cpp @@ -46,8 +46,8 @@ LLFloaterGLTFAssetEditor::LLFloaterGLTFAssetEditor(const LLSD& key) { setTitle("GLTF Asset Editor (WIP)"); - mCommitCallbackRegistrar.add("PanelObject.menuDoToSelected", [this](LLUICtrl* ctrl, const LLSD& data) { onMenuDoToSelected(data); }); - mEnableCallbackRegistrar.add("PanelObject.menuEnable", [this](LLUICtrl* ctrl, const LLSD& data) { return onMenuEnableItem(data); }); + mCommitCallbackRegistrar.add("PanelObject.menuDoToSelected", { [this](LLUICtrl* ctrl, const LLSD& data) { onMenuDoToSelected(data); }}); + mEnableCallbackRegistrar.add("PanelObject.menuEnable", { [this](LLUICtrl* ctrl, const LLSD& data) { return onMenuEnableItem(data); }}); } LLFloaterGLTFAssetEditor::~LLFloaterGLTFAssetEditor() diff --git a/indra/newview/llfloatersettingscolor.cpp b/indra/newview/llfloatersettingscolor.cpp index d9c382a1dc..1ca2412907 100644 --- a/indra/newview/llfloatersettingscolor.cpp +++ b/indra/newview/llfloatersettingscolor.cpp @@ -43,8 +43,8 @@ LLFloaterSettingsColor::LLFloaterSettingsColor(const LLSD& key) : LLFloater(key), mSettingList(NULL) { - mCommitCallbackRegistrar.add("CommitSettings", boost::bind(&LLFloaterSettingsColor::onCommitSettings, this)); - mCommitCallbackRegistrar.add("ClickDefault", boost::bind(&LLFloaterSettingsColor::onClickDefault, this)); + mCommitCallbackRegistrar.add("CommitSettings", { boost::bind(&LLFloaterSettingsColor::onCommitSettings, this) }); + mCommitCallbackRegistrar.add("ClickDefault", { boost::bind(&LLFloaterSettingsColor::onClickDefault, this) }); } LLFloaterSettingsColor::~LLFloaterSettingsColor() diff --git a/indra/newview/llfloatersettingsdebug.cpp b/indra/newview/llfloatersettingsdebug.cpp index 4a053a99de..0a01fff36d 100644 --- a/indra/newview/llfloatersettingsdebug.cpp +++ b/indra/newview/llfloatersettingsdebug.cpp @@ -66,7 +66,6 @@ bool LLFloaterSettingsDebug::postBuild() mSettingNameText = getChild("setting_name_txt"); mComment = getChild("comment_text"); - mSettingName = getChild("setting_name_txt"); mLLSDVal = getChild("llsd_text"); mCopyBtn = getChild("copy_btn"); @@ -658,6 +657,6 @@ void LLFloaterSettingsDebug::hideUIControls() void LLFloaterSettingsDebug::onClickCopy() { - std::string setting_name = mSettingName->getText(); + std::string setting_name = mSettingNameText->getText(); LLClipboard::instance().copyToClipboard(utf8str_to_wstring(setting_name), 0, narrow(setting_name.size())); } diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp index 6f567e67f8..c389d04443 100644 --- a/indra/newview/lluilistener.cpp +++ b/indra/newview/lluilistener.cpp @@ -255,10 +255,11 @@ LLMenuGL::Params get_params(const LLSD&event) LLMenuGL* get_parent_menu(LLEventAPI::Response& response, const LLSD&event) { - LLMenuGL* parent_menu = gMenuBarView->findChildMenuByName(event["parent_menu"], true); + auto parent_menu_name{ event["parent_menu"].asString() }; + LLMenuGL* parent_menu = gMenuBarView->findChildMenuByName(parent_menu_name, true); if(!parent_menu) { - response.error(stringize("Parent menu ", std::quoted(event["parent_menu"].asString()), " was not found")); + response.error(stringize("Parent menu ", std::quoted(parent_menu_name), " was not found")); } return parent_menu; } -- cgit v1.2.3 From 25a86618002a397d1d8dabf2ec1f093489b2f816 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 5 Sep 2024 13:41:41 -0400 Subject: Fix Windows build errors from develop => release/luau-scripting. --- indra/llcommon/coro_scheduler.cpp | 4 ++-- indra/llcommon/coro_scheduler.h | 8 ++++---- indra/llcommon/llcallbacklist.cpp | 6 +++--- indra/newview/llinventorylistener.cpp | 2 +- indra/newview/llluamanager.cpp | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/coro_scheduler.cpp b/indra/llcommon/coro_scheduler.cpp index 393356a39b..02b9f11333 100644 --- a/indra/llcommon/coro_scheduler.cpp +++ b/indra/llcommon/coro_scheduler.cpp @@ -26,7 +26,7 @@ namespace llcoro { -const F32 scheduler::DEFAULT_TIMESLICE{ LL::Timers::DEFAULT_TIMESLICE }; +const F64 scheduler::DEFAULT_TIMESLICE{ LL::Timers::DEFAULT_TIMESLICE }; const std::string qname("General"); @@ -72,7 +72,7 @@ boost::fibers::context* scheduler::pick_next() noexcept // When the main fiber is ready, and it's been more than mTimeslice since // the main fiber last ran, it's time to intervene. - F32 elapsed(now - mMainLast); + F64 elapsed(now - mMainLast); if (mMainCtx && elapsed > mTimeslice) { // We claim that the main fiber is not only stored in mMainCtx, but is diff --git a/indra/llcommon/coro_scheduler.h b/indra/llcommon/coro_scheduler.h index cc7e75d798..eee2d746b5 100644 --- a/indra/llcommon/coro_scheduler.h +++ b/indra/llcommon/coro_scheduler.h @@ -38,7 +38,7 @@ class scheduler: public boost::fibers::algo::round_robin public: // If the main fiber is ready, and it's been at least this long since the // main fiber last ran, jump the main fiber to the head of the queue. - static const F32 DEFAULT_TIMESLICE; + static const F64 DEFAULT_TIMESLICE; scheduler(); void awakened( boost::fibers::context*) noexcept override; @@ -57,11 +57,11 @@ private: bool mMainRunning{ false }; // If it's been at least this long since the last time the main fiber got // control, jump it to the head of the queue. - F32 mTimeslice{ DEFAULT_TIMESLICE }; + F64 mTimeslice{ DEFAULT_TIMESLICE }; // Timestamp as of the last time we suspended the main fiber. - F32 mMainLast{ 0 }; + F64 mMainLast{ 0 }; // Timestamp of start time - F32 mStart{ 0 }; + F64 mStart{ 0 }; // count context switches U64 mSwitches{ 0 }; // WorkQueue for deferred logging diff --git a/indra/llcommon/llcallbacklist.cpp b/indra/llcommon/llcallbacklist.cpp index 7cbe7a8c02..7b05c25c21 100644 --- a/indra/llcommon/llcallbacklist.cpp +++ b/indra/llcommon/llcallbacklist.cpp @@ -205,7 +205,7 @@ F32 Timers::timeUntilCall(handle_t timer) const } else { - return found->second.mTime - now(); + return narrow(found->second.mTime - now()); } } @@ -436,7 +436,7 @@ void TimersListener::scheduleAfter(const LLSD& params) // ditch mHandles entry mHandles.erase(key); }, - after)); + narrow(after))); } void TimersListener::scheduleEvery(const LLSD& params) @@ -461,7 +461,7 @@ void TimersListener::scheduleEvery(const LLSD& params) // we can't use a handshake -- always keep the ball rolling return false; }, - every)); + narrow(every))); } LLSD TimersListener::cancel(const LLSD& params) diff --git a/indra/newview/llinventorylistener.cpp b/indra/newview/llinventorylistener.cpp index 9263663997..753ad3ddeb 100644 --- a/indra/newview/llinventorylistener.cpp +++ b/indra/newview/llinventorylistener.cpp @@ -33,7 +33,7 @@ #include "llwearableitemslist.h" #include "stringize.h" -static const F32 MAX_ITEM_LIMIT = 100; +constexpr S32 MAX_ITEM_LIMIT = 100; LLInventoryListener::LLInventoryListener() : LLEventAPI("LLInventory", diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 91a34345be..6a725e785f 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -56,9 +56,9 @@ std::map LLLUAmanager::sScriptNames; lua_function(sleep, "sleep(seconds): pause the running coroutine") { lua_checkdelta(L, -1); - F32 seconds = lua_tonumber(L, -1); + lua_Number seconds = lua_tonumber(L, -1); lua_pop(L, 1); - llcoro::suspendUntilTimeout(seconds); + llcoro::suspendUntilTimeout(narrow(seconds)); LuaState::getParent(L).set_interrupts_counter(0); return 0; }; -- cgit v1.2.3 From 89992713218dba9f1a15973decad897127e90545 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 5 Sep 2024 17:31:14 -0400 Subject: Fix typo in cppfeatures_test.cpp --- indra/newview/tests/cppfeatures_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/tests/cppfeatures_test.cpp b/indra/newview/tests/cppfeatures_test.cpp index f5ea3a522b..ca94dcfc95 100644 --- a/indra/newview/tests/cppfeatures_test.cpp +++ b/indra/newview/tests/cppfeatures_test.cpp @@ -283,7 +283,7 @@ void cpp_features_test_object_t::test<8>() ensure("init member inline 1", ii.mFoo==10); InitInlineWithConstructor iici; - ensure("init member inline 2", iici.mFoo=10); + ensure("init member inline 2", iici.mFoo==10); ensure("init member inline 3", iici.mBar==25); } -- cgit v1.2.3 From c816fefb3de3b9b5c0421cf446bacfe1284c13a5 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 5 Sep 2024 17:33:13 -0400 Subject: Avoid some classic-C style pointer casts. --- indra/llmeshoptimizer/llmeshoptimizer.cpp | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'indra') diff --git a/indra/llmeshoptimizer/llmeshoptimizer.cpp b/indra/llmeshoptimizer/llmeshoptimizer.cpp index 7339454367..9d62a72188 100644 --- a/indra/llmeshoptimizer/llmeshoptimizer.cpp +++ b/indra/llmeshoptimizer/llmeshoptimizer.cpp @@ -57,7 +57,7 @@ void LLMeshOptimizer::generateShadowIndexBufferU32(U32 *destination, S32 index = 0; if (vertex_positions) { - streams[index].data = (const float*)vertex_positions; + streams[index].data = vertex_positions->getF32ptr(); // Despite being LLVector4a, only x, y and z are in use streams[index].size = sizeof(F32) * 3; streams[index].stride = sizeof(F32) * 4; @@ -65,14 +65,14 @@ void LLMeshOptimizer::generateShadowIndexBufferU32(U32 *destination, } if (normals) { - streams[index].data = (const float*)normals; + streams[index].data = normals->getF32ptr(); streams[index].size = sizeof(F32) * 3; streams[index].stride = sizeof(F32) * 4; index++; } if (text_coords) { - streams[index].data = (const float*)text_coords; + streams[index].data = text_coords->mV; streams[index].size = sizeof(F32) * 2; streams[index].stride = sizeof(F32) * 2; index++; @@ -108,21 +108,21 @@ void LLMeshOptimizer::generateShadowIndexBufferU16(U16 *destination, S32 index = 0; if (vertex_positions) { - streams[index].data = (const float*)vertex_positions; + streams[index].data = vertex_positions->getF32ptr(); streams[index].size = sizeof(F32) * 3; streams[index].stride = sizeof(F32) * 4; index++; } if (normals) { - streams[index].data = (const float*)normals; + streams[index].data = normals->getF32ptr(); streams[index].size = sizeof(F32) * 3; streams[index].stride = sizeof(F32) * 4; index++; } if (text_coords) { - streams[index].data = (const float*)text_coords; + streams[index].data = text_coords->mV; streams[index].size = sizeof(F32) * 2; streams[index].stride = sizeof(F32) * 2; index++; @@ -162,9 +162,9 @@ size_t LLMeshOptimizer::generateRemapMultiU32( U64 vertex_count) { meshopt_Stream streams[] = { - {(const float*)vertex_positions, sizeof(F32) * 3, sizeof(F32) * 4}, - {(const float*)normals, sizeof(F32) * 3, sizeof(F32) * 4}, - {(const float*)text_coords, sizeof(F32) * 2, sizeof(F32) * 2}, + {vertex_positions->getF32ptr(), sizeof(F32) * 3, sizeof(F32) * 4}, + {normals->getF32ptr(), sizeof(F32) * 3, sizeof(F32) * 4}, + {text_coords->mV, sizeof(F32) * 2, sizeof(F32) * 2}, }; // Remap can function without indices, @@ -236,7 +236,7 @@ void LLMeshOptimizer::remapPositionsBuffer(LLVector4a * destination_vertices, U64 vertex_count, const unsigned int* remap) { - meshopt_remapVertexBuffer((float*)destination_vertices, (const float*)vertex_positions, vertex_count, sizeof(LLVector4a), remap); + meshopt_remapVertexBuffer(destination_vertices->getF32ptr(), vertex_positions->getF32ptr(), vertex_count, sizeof(LLVector4a), remap); } void LLMeshOptimizer::remapNormalsBuffer(LLVector4a * destination_normalss, @@ -244,7 +244,7 @@ void LLMeshOptimizer::remapNormalsBuffer(LLVector4a * destination_normalss, U64 mormals_count, const unsigned int* remap) { - meshopt_remapVertexBuffer((float*)destination_normalss, (const float*)normals, mormals_count, sizeof(LLVector4a), remap); + meshopt_remapVertexBuffer(destination_normalss->getF32ptr(), normals->getF32ptr(), mormals_count, sizeof(LLVector4a), remap); } void LLMeshOptimizer::remapUVBuffer(LLVector2 * destination_uvs, @@ -252,7 +252,7 @@ void LLMeshOptimizer::remapUVBuffer(LLVector2 * destination_uvs, U64 uv_count, const unsigned int* remap) { - meshopt_remapVertexBuffer((float*)destination_uvs, (const float*)uv_positions, uv_count, sizeof(LLVector2), remap); + meshopt_remapVertexBuffer(destination_uvs->mV, uv_positions->mV, uv_count, sizeof(LLVector2), remap); } //static @@ -273,7 +273,7 @@ U64 LLMeshOptimizer::simplifyU32(U32 *destination, return meshopt_simplifySloppy(destination, indices, index_count, - (const float*)vertex_positions, + vertex_positions->getF32ptr(), vertex_count, vertex_positions_stride, target_index_count, @@ -286,7 +286,7 @@ U64 LLMeshOptimizer::simplifyU32(U32 *destination, return meshopt_simplify(destination, indices, index_count, - (const float*)vertex_positions, + vertex_positions->getF32ptr(), vertex_count, vertex_positions_stride, target_index_count, @@ -315,7 +315,7 @@ U64 LLMeshOptimizer::simplify(U16 *destination, return meshopt_simplifySloppy(destination, indices, index_count, - (const float*)vertex_positions, + vertex_positions->getF32ptr(), vertex_count, vertex_positions_stride, target_index_count, @@ -328,7 +328,7 @@ U64 LLMeshOptimizer::simplify(U16 *destination, return meshopt_simplify(destination, indices, index_count, - (const float*)vertex_positions, + vertex_positions->getF32ptr(), vertex_count, vertex_positions_stride, target_index_count, -- cgit v1.2.3 From 2841ca7229e076f9ddd1b2ec2e77099bab7753d9 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Fri, 6 Sep 2024 00:11:01 +0200 Subject: Fix remaining occurrences of fake BOOL --- indra/newview/llfloaterluadebug.cpp | 2 +- indra/newview/llfloaterluascripts.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/newview/llfloaterluadebug.cpp b/indra/newview/llfloaterluadebug.cpp index dc989fe15d..06877df816 100644 --- a/indra/newview/llfloaterluadebug.cpp +++ b/indra/newview/llfloaterluadebug.cpp @@ -69,7 +69,7 @@ bool LLFloaterLUADebug::postBuild() mLineInput->setCommitCallback(boost::bind(&LLFloaterLUADebug::onExecuteClicked, this)); mLineInput->setSelectAllonCommit(false); - return TRUE; + return true; } LLFloaterLUADebug::~LLFloaterLUADebug() diff --git a/indra/newview/llfloaterluascripts.cpp b/indra/newview/llfloaterluascripts.cpp index 79623cdefb..0eba45ec29 100644 --- a/indra/newview/llfloaterluascripts.cpp +++ b/indra/newview/llfloaterluascripts.cpp @@ -72,7 +72,7 @@ bool LLFloaterLUAScripts::postBuild() mContextMenuHandle = menu->getHandle(); } - return TRUE; + return true; } LLFloaterLUAScripts::~LLFloaterLUAScripts() -- cgit v1.2.3 From 8de8daca601dc85e2b73687856f0a321016b4463 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Sep 2024 12:31:24 -0400 Subject: Introduce llless(), and use it for llmin(), llmax(). Add tests to verify that llless() correctly handles signed <=> unsigned comparison, which native "<" does not. --- indra/llcommon/lldefs.h | 46 +++++++++++++++++++++- indra/llcommon/tests/llcond_test.cpp | 75 ++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lldefs.h b/indra/llcommon/lldefs.h index 2fbb26dc1a..ed075e8d96 100644 --- a/indra/llcommon/lldefs.h +++ b/indra/llcommon/lldefs.h @@ -28,6 +28,7 @@ #define LL_LLDEFS_H #include "stdtypes.h" +#include #include // Often used array indices @@ -169,6 +170,31 @@ constexpr U32 MAXADDRSTR = 17; // 123.567.901.345 = 15 chars + \0 + // llclampb(a) // clamps a to [0 .. 255] // +// llless(d0, d1) safely compares d0 < d1 even if one is signed and the other +// is unsigned. A simple (d0 < d1) expression converts the signed operand to +// unsigned before comparing. If the signed operand is negative, that flips +// the negative value to a huge positive value, producing the wrong answer! +// llless() specifically addresses that case. +template +inline bool llless(T0 d0, T1 d1) +{ + if constexpr (std::is_signed_v && ! std::is_signed_v) + { + // T0 signed, T1 unsigned: negative d0 is less than any unsigned d1 + if (d0 < 0) + return true; + } + else if constexpr (! std::is_signed_v && std::is_signed_v) + { + // T0 unsigned, T1 signed: any unsigned d0 is greater than negative d1 + if (d1 < 0) + return false; + } + // both T0 and T1 are signed, or both are unsigned, or both non-negative: + // straightforward comparison works + return d0 < d1; +} + // recursion tail template inline auto llmax(T data) @@ -180,7 +206,7 @@ template inline auto llmax(T0 d0, T1 d1, Ts... rest) { auto maxrest = llmax(d1, rest...); - return (d0 > maxrest)? d0 : maxrest; + return llless(maxrest, d0)? d0 : maxrest; } // recursion tail @@ -194,12 +220,28 @@ template inline auto llmin(T0 d0, T1 d1, Ts... rest) { auto minrest = llmin(d1, rest...); - return (d0 < minrest) ? d0 : minrest; + return llless(d0, minrest) ? d0 : minrest; } template inline A llclamp(A a, MIN minval, MAX maxval) { + // The only troublesome case is if A is unsigned and either minval or + // maxval is both signed and negative. Casting a negative number to + // unsigned flips it to a huge positive number, making this llclamp() call + // ineffective. + if constexpr (! std::is_signed_v) + { + if constexpr (std::is_signed_v) + { + assert(minval >= 0); + } + if constexpr (std::is_signed_v) + { + assert(maxval >= 0); + } + } + A aminval{ static_cast(minval) }, amaxval{ static_cast(maxval) }; if ( a < aminval ) { diff --git a/indra/llcommon/tests/llcond_test.cpp b/indra/llcommon/tests/llcond_test.cpp index f2a302ed13..7d1ac6edb9 100644 --- a/indra/llcommon/tests/llcond_test.cpp +++ b/indra/llcommon/tests/llcond_test.cpp @@ -19,6 +19,7 @@ // other Linden headers #include "../test/lltut.h" #include "llcoros.h" +#include "lldefs.h" // llless() /***************************************************************************** * TUT @@ -64,4 +65,78 @@ namespace tut cond.set_all(2); cond.wait_equal(3); } + + template + struct compare + { + const char* desc; + T0 lhs; + T1 rhs; + bool expect; + + void test() const + { + // fails +// ensure_equals(desc, (lhs < rhs), expect); + ensure_equals(desc, llless(lhs, rhs), expect); + } + }; + + template<> template<> + void object::test<3>() + { + set_test_name("comparison"); + // Try to construct signed and unsigned variables such that the + // compiler can't optimize away the code to compare at runtime. + std::istringstream input("-1 10 20 10 20"); + int minus1, s10, s20; + input >> minus1 >> s10 >> s20; + unsigned u10, u20; + input >> u10 >> u20; + ensure_equals("minus1 wrong", minus1, -1); + ensure_equals("s10 wrong", s10, 10); + ensure_equals("s20 wrong", s20, 20); + ensure_equals("u10 wrong", u10, 10); + ensure_equals("u20 wrong", u20, 20); + // signed < signed should always work! + compare ss[] = + { {"minus1 < s10", minus1, s10, true}, + {"s10 < s10", s10, s10, false}, + {"s20 < s10", s20, s20, false} + }; + for (const auto& cmp : ss) + { + cmp.test(); + } + // unsigned < unsigned should always work! + compare uu[] = + { {"u10 < u20", u10, u20, true}, + {"u20 < u20", u20, u20, false}, + {"u20 < u10", u20, u10, false} + }; + for (const auto& cmp : uu) + { + cmp.test(); + } + // signed < unsigned ?? + compare su[] = + { {"minus1 < u10", minus1, u10, true}, + {"s10 < u10", s10, u10, false}, + {"s20 < u10", s20, u10, false} + }; + for (const auto& cmp : su) + { + cmp.test(); + } + // unsigned < signed ?? + compare us[] = + { {"u10 < minus1", u10, minus1, false}, + {"u10 < s10", u10, s10, false}, + {"u10 < s20", u10, s20, true} + }; + for (const auto& cmp : us) + { + cmp.test(); + } + } } // namespace tut -- cgit v1.2.3 From 0a3e5074bbacdd9184311bd99a74913d1ce0423c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Sep 2024 14:43:05 -0400 Subject: Allow LLMenuGL::insert() to append as well as inserting. Appending is effected by passing position == getItemCount(). Until now, insert() disallowed that value, so you could insert before the last existing entry but not after it. --- indra/llui/llmenugl.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 69ffa9a94f..cc770ca90a 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -2625,7 +2625,9 @@ void LLMenuGL::insert( S32 position, LLView * ctrl, bool arrange /*= true*/ ) { LLMenuItemGL * item = dynamic_cast(ctrl); - if (NULL == item || position < 0 || position >= mItems.size()) + // If position == size(), std::advance() will return end() -- which is + // okay, because insert(end()) is the same as append(). + if (NULL == item || position < 0 || position > mItems.size()) { return; } -- cgit v1.2.3 From 13ec4bccb534ef55a773004bd3c5a5ac9027600b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Sep 2024 15:06:54 -0400 Subject: Add pos to 'UI' listener's 'addMenuItem' and 'addMenuSeparator' 'pos' is a 0-relative index at which to insert the desired menu item or separator. If 'pos' is omitted, the item is appended to the menu. --- indra/newview/lluilistener.cpp | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) (limited to 'indra') diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp index c389d04443..44eb8461f1 100644 --- a/indra/newview/lluilistener.cpp +++ b/indra/newview/lluilistener.cpp @@ -90,12 +90,14 @@ LLUIListener::LLUIListener(): add("addMenuItem", "Add new menu item [\"name\"] with displayed [\"label\"]\n" "and call-on-click UI function [\"func\"] with optional [\"param\"]\n" - "to the [\"parent_menu\"] within the Top menu.", + "to the [\"parent_menu\"] within the Top menu.\n" + "If [\"pos\"] is present, insert at specified 0-relative position.", &LLUIListener::addMenuItem, required_args.with("func", LLSD())); add("addMenuSeparator", - "Add menu separator to the [\"parent_menu\"] within the Top menu.", + "Add menu separator to the [\"parent_menu\"] within the Top menu.\n" + "If [\"pos\"] is present, insert at specified 0-relative position.", &LLUIListener::addMenuSeparator, llsd::map("parent_menu", LLSD(), "reply", LLSD())); @@ -264,6 +266,13 @@ LLMenuGL* get_parent_menu(LLEventAPI::Response& response, const LLSD&event) return parent_menu; } +// Return event["pos"].asInteger() if passed, but clamp (0 <= pos <= size). +// Reserve -1 return to mean event has no "pos" key. +S32 get_pos(const LLSD& event, U32 size) +{ + return event["pos"].isInteger()? llclamp(event["pos"].asInteger(), 0, size) : -1; +} + void LLUIListener::addMenu(const LLSD&event) const { Response response(LLSD(), event); @@ -302,9 +311,19 @@ void LLUIListener::addMenuItem(const LLSD&event) const item_params.on_click = item_func; if(LLMenuGL* parent_menu = get_parent_menu(response, event)) { - if(!parent_menu->append(LLUICtrlFactory::create(item_params))) + auto item = LLUICtrlFactory::create(item_params); + // Clamp pos to getItemCount(), meaning append. If pos exceeds that, + // insert() will silently ignore the request. + auto pos = get_pos(event, parent_menu->getItemCount()); + if (pos >= 0) { - response.error(stringize("Menu item ", std::quoted(event["name"].asString()), " was not added")); + // insert() returns void: we just have to assume it worked. + parent_menu->insert(pos, item); + } + else if (! parent_menu->append(item)) + { + response.error(stringize("Menu item ", std::quoted(event["name"].asString()), + " was not added")); } } } @@ -314,7 +333,19 @@ void LLUIListener::addMenuSeparator(const LLSD&event) const Response response(LLSD(), event); if(LLMenuGL* parent_menu = get_parent_menu(response, event)) { - if(!parent_menu->addSeparator()) + // Clamp pos to getItemCount(), meaning append. If pos exceeds that, + // insert() will silently ignore the request. + auto pos = get_pos(event, parent_menu->getItemCount()); + if (pos >= 0) + { + // Even though addSeparator() does not accept a position, + // LLMenuItemSeparatorGL isa LLMenuItemGL, so we can use insert(). + LLMenuItemSeparatorGL::Params p; + LLMenuItemGL* separator = LLUICtrlFactory::create(p); + // insert() returns void: we just have to assume it worked. + parent_menu->insert(pos, separator); + } + else if (! parent_menu->addSeparator()) { response.error("Separator was not added"); } -- cgit v1.2.3 From 782a898efadadf2747cc3310749f34a8dde8dd60 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Sep 2024 15:11:15 -0400 Subject: Introduce LuaFeature debug setting, default off. Make central Lua engine functionality conditional on that flag. --- indra/llcommon/lua_function.cpp | 160 ++++++++++++++++++++------------ indra/llcommon/lua_function.h | 5 +- indra/newview/app_settings/settings.xml | 11 +++ 3 files changed, 117 insertions(+), 59 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index f7876e4aaf..eefb1e62cf 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -24,6 +24,7 @@ #include // external library headers // other Linden headers +#include "commoncontrol.h" #include "fsyspath.h" #include "hexdump.h" #include "llcoros.h" @@ -529,9 +530,35 @@ int lua_metaipair(lua_State* L); } // anonymous namespace LuaState::LuaState(script_finished_fn cb): - mCallback(cb), - mState(luaL_newstate()) + mCallback(cb) { + /*---------------------------- feature flag ----------------------------*/ + try + { + mFeature = LL::CommonControl::get("Global", "LuaFeature").asBoolean(); + } + catch (const LL::CommonControl::NoListener&) + { + // If this program doesn't have an LLViewerControlListener, + // it's probably a test program; go ahead. + mFeature = true; + } + catch (const LL::CommonControl::ParamError&) + { + // We found LLViewerControlListener, but its settings do not include + // "LuaFeature". Hmm, fishy: that feature flag was introduced at the + // same time as this code. + mFeature = false; + } + // None of the rest of this is necessary if we're not going to run anything. + if (! mFeature) + { + mError = "Lua feature disabled"; + return; + } + /*---------------------------- feature flag ----------------------------*/ + + mState = luaL_newstate(); // Ensure that we can always find this LuaState instance, given the // lua_State we just created or any of its coroutines. sLuaStateMap.emplace(mState, this); @@ -682,75 +709,81 @@ int lua_metaipair(lua_State* L) LuaState::~LuaState() { - // We're just about to destroy this lua_State mState. Did this Lua chunk - // register any atexit() functions? - lluau_checkstack(mState, 3); - // look up Registry.atexit - lua_getfield(mState, LUA_REGISTRYINDEX, "atexit"); - // stack contains Registry.atexit - if (lua_istable(mState, -1)) + /*---------------------------- feature flag ----------------------------*/ + if (mFeature) + /*---------------------------- feature flag ----------------------------*/ { - // We happen to know that Registry.atexit is built by appending array - // entries using table.insert(). That's important because it means - // there are no holes, and therefore lua_objlen() should be correct. - // That's important because we walk the atexit table backwards, to - // destroy last the things we created (passed to LL.atexit()) first. - int len(lua_objlen(mState, -1)); - LL_DEBUGS("Lua") << LLCoros::getName() << ": Registry.atexit is a table with " - << len << " entries" << LL_ENDL; - - // Push debug.traceback() onto the stack as lua_pcall()'s error - // handler function. On error, lua_pcall() calls the specified error - // handler function with the original error message; the message - // returned by the error handler is then returned by lua_pcall(). - // Luau's debug.traceback() is called with a message to prepend to the - // returned traceback string. Almost as if they'd been designed to - // work together... - lua_getglobal(mState, "debug"); - lua_getfield(mState, -1, "traceback"); - // ditch "debug" - lua_remove(mState, -2); - // stack now contains atexit, debug.traceback() - - for (int i(len); i >= 1; --i) + // We're just about to destroy this lua_State mState. Did this Lua chunk + // register any atexit() functions? + lluau_checkstack(mState, 3); + // look up Registry.atexit + lua_getfield(mState, LUA_REGISTRYINDEX, "atexit"); + // stack contains Registry.atexit + if (lua_istable(mState, -1)) { - lua_pushinteger(mState, i); - // stack contains Registry.atexit, debug.traceback(), i - lua_gettable(mState, -3); - // stack contains Registry.atexit, debug.traceback(), atexit[i] - // Call atexit[i](), no args, no return values. - // Use lua_pcall() because errors in any one atexit() function - // shouldn't cancel the rest of them. Pass debug.traceback() as - // the error handler function. - LL_DEBUGS("Lua") << LLCoros::getName() - << ": calling atexit(" << i << ")" << LL_ENDL; - if (lua_pcall(mState, 0, 0, -2) != LUA_OK) + // We happen to know that Registry.atexit is built by appending array + // entries using table.insert(). That's important because it means + // there are no holes, and therefore lua_objlen() should be correct. + // That's important because we walk the atexit table backwards, to + // destroy last the things we created (passed to LL.atexit()) first. + int len(lua_objlen(mState, -1)); + LL_DEBUGS("Lua") << LLCoros::getName() << ": Registry.atexit is a table with " + << len << " entries" << LL_ENDL; + + // Push debug.traceback() onto the stack as lua_pcall()'s error + // handler function. On error, lua_pcall() calls the specified error + // handler function with the original error message; the message + // returned by the error handler is then returned by lua_pcall(). + // Luau's debug.traceback() is called with a message to prepend to the + // returned traceback string. Almost as if they'd been designed to + // work together... + lua_getglobal(mState, "debug"); + lua_getfield(mState, -1, "traceback"); + // ditch "debug" + lua_remove(mState, -2); + // stack now contains atexit, debug.traceback() + + for (int i(len); i >= 1; --i) { - auto error{ lua_tostdstring(mState, -1) }; - LL_WARNS("Lua") << LLCoros::getName() - << ": atexit(" << i << ") error: " << error << LL_ENDL; - // pop error message - lua_pop(mState, 1); + lua_pushinteger(mState, i); + // stack contains Registry.atexit, debug.traceback(), i + lua_gettable(mState, -3); + // stack contains Registry.atexit, debug.traceback(), atexit[i] + // Call atexit[i](), no args, no return values. + // Use lua_pcall() because errors in any one atexit() function + // shouldn't cancel the rest of them. Pass debug.traceback() as + // the error handler function. + LL_DEBUGS("Lua") << LLCoros::getName() + << ": calling atexit(" << i << ")" << LL_ENDL; + if (lua_pcall(mState, 0, 0, -2) != LUA_OK) + { + auto error{ lua_tostdstring(mState, -1) }; + LL_WARNS("Lua") << LLCoros::getName() + << ": atexit(" << i << ") error: " << error << LL_ENDL; + // pop error message + lua_pop(mState, 1); + } + LL_DEBUGS("Lua") << LLCoros::getName() << ": atexit(" << i << ") done" << LL_ENDL; + // lua_pcall() has already popped atexit[i]: + // stack contains atexit, debug.traceback() } - LL_DEBUGS("Lua") << LLCoros::getName() << ": atexit(" << i << ") done" << LL_ENDL; - // lua_pcall() has already popped atexit[i]: - // stack contains atexit, debug.traceback() + // pop debug.traceback() + lua_pop(mState, 1); } - // pop debug.traceback() + // pop Registry.atexit (either table or nil) lua_pop(mState, 1); - } - // pop Registry.atexit (either table or nil) - lua_pop(mState, 1); - lua_close(mState); + // with the demise of this LuaState, remove sLuaStateMap entry + sLuaStateMap.erase(mState); + + lua_close(mState); + } if (mCallback) { // mError potentially set by previous checkLua() call(s) mCallback(mError); } - // with the demise of this LuaState, remove sLuaStateMap entry - sLuaStateMap.erase(mState); } bool LuaState::checkLua(const std::string& desc, int r) @@ -768,6 +801,14 @@ bool LuaState::checkLua(const std::string& desc, int r) std::pair LuaState::expr(const std::string& desc, const std::string& text) { + /*---------------------------- feature flag ----------------------------*/ + if (! mFeature) + { + // fake an error + return { -1, stringize("Not running ", desc) }; + } + /*---------------------------- feature flag ----------------------------*/ + set_interrupts_counter(0); lua_callbacks(mState)->interrupt = [](lua_State *L, int gc) @@ -843,6 +884,9 @@ std::pair LuaState::expr(const std::string& desc, const std::string& return result; } +// We think we don't need mFeature tests in the rest of these LuaState methods +// because, if expr() isn't running code, nobody should be calling any of them. + LuaListener& LuaState::obtainListener(lua_State* L) { lluau_checkstack(L, 2); diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index e28656c03b..967d8eaba1 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -115,8 +115,11 @@ public: void check_interrupts_counter(); private: + /*---------------------------- feature flag ----------------------------*/ + bool mFeature{ false }; + /*---------------------------- feature flag ----------------------------*/ script_finished_fn mCallback; - lua_State* mState; + lua_State* mState{ nullptr }; std::string mError; S32 mInterrupts{ 0 }; }; diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 248992fd07..c6946d1ec1 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -3999,6 +3999,17 @@ scripts/lua + LuaFeature + + Comment + Enable viewer's Lua script engine. + Persist + 1 + Type + Boolean + Value + 0 + LuaRequirePath Comment -- cgit v1.2.3 From 8c68abb2f63d54aeb4614c63a109b838ed8d0656 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Sep 2024 15:14:59 -0400 Subject: Remove Lua floaters from menu_viewer.xml; re-add if Lua enabled. Add a menus.lua autorun script that waits until login, then adds the Lua floaters back into the Develop->Consoles menu where they were originally. Extend UI.addMenuItem() and addMenuSeparator() to support pos argument. --- indra/newview/scripts/lua/auto/menus.lua | 51 ++++++++++++++++++++++ indra/newview/scripts/lua/require/UI.lua | 4 +- indra/newview/skins/default/xui/en/menu_viewer.xml | 21 --------- 3 files changed, 53 insertions(+), 23 deletions(-) create mode 100644 indra/newview/scripts/lua/auto/menus.lua (limited to 'indra') diff --git a/indra/newview/scripts/lua/auto/menus.lua b/indra/newview/scripts/lua/auto/menus.lua new file mode 100644 index 0000000000..b2f54d83df --- /dev/null +++ b/indra/newview/scripts/lua/auto/menus.lua @@ -0,0 +1,51 @@ +-- Inject Lua-related menus into the top menu structure. Run this as a Lua +-- script so that turning off the Lua feature also disables these menus. + +-- Under Develop -> Consoles, want to present the equivalent of: +-- +-- +-- +-- +-- +-- +-- +-- +-- + +local startup = require 'startup' +local UI = require 'UI' + +-- Don't mess with the viewer's menu structure until we've logged in. +startup.wait('STATE_STARTED') + +-- Add LUA Debug Console to Develop->Consoles +local pos = 9 +UI.addMenuSeparator{ + parent_menu='Consoles', pos=pos, +} +pos += 1 +UI.addMenuItem{ + parent_menu='Consoles', pos=pos, + name='lua_debug', label='LUA Debug Console', + func='Floater.ToggleOrBringToFront', param='lua_debug', +} +pos += 1 + +-- Add LUA Scripts Info to Develop->Consoles +UI.addMenuItem{ + parent_menu='Consoles', pos=pos, + name='lua_scripts', label='LUA Scripts Info', + func='Floater.ToggleOrBringToFront', param='lua_scripts', +} diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua index 73a76fa6b8..cf2695917e 100644 --- a/indra/newview/scripts/lua/require/UI.lua +++ b/indra/newview/scripts/lua/require/UI.lua @@ -170,13 +170,13 @@ end -- see UI.callables() for valid values of 'func' function UI.addMenuItem(...) - local args = mapargs('name,label,parent_menu,func,param', ...) + local args = mapargs('name,label,parent_menu,func,param,pos', ...) args.op = 'addMenuItem' return leap.request('UI', args) end function UI.addMenuSeparator(...) - local args = mapargs('parent_menu', ...) + local args = mapargs('parent_menu,pos', ...) args.op = 'addMenuSeparator' return leap.request('UI', args) end diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 8999797049..7fe9d0efe5 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -2565,27 +2565,6 @@ function="World.EnvPreset" parameter="scene monitor" /> - - - - - - - - - Date: Fri, 6 Sep 2024 16:57:12 -0400 Subject: Avoid VC fatal warning when trying to fix un/signed comparison. --- indra/llcommon/lldefs.h | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lldefs.h b/indra/llcommon/lldefs.h index ed075e8d96..d4b063f88c 100644 --- a/indra/llcommon/lldefs.h +++ b/indra/llcommon/lldefs.h @@ -183,16 +183,23 @@ inline bool llless(T0 d0, T1 d1) // T0 signed, T1 unsigned: negative d0 is less than any unsigned d1 if (d0 < 0) return true; + // both are non-negative: explicitly cast to avoid C4018 + return std::make_unsigned_t(d0) < d1; } else if constexpr (! std::is_signed_v && std::is_signed_v) { // T0 unsigned, T1 signed: any unsigned d0 is greater than negative d1 if (d1 < 0) return false; + // both are non-negative: explicitly cast to avoid C4018 + return d0 < std::make_unsigned_t(d1); + } + else + { + // both T0 and T1 are signed, or both are unsigned: + // straightforward comparison works + return d0 < d1; } - // both T0 and T1 are signed, or both are unsigned, or both non-negative: - // straightforward comparison works - return d0 < d1; } // recursion tail -- cgit v1.2.3 From b8678d8fa3521f4496ddc569de391633711e46cb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 Sep 2024 09:56:36 -0400 Subject: Fix a couple errors from merging in new code. --- indra/newview/llviewermenu.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index a5d199d9d7..3b45dd71a7 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -9768,8 +9768,8 @@ void initialize_menus() registrar.add("Agent.ToggleMicrophone", boost::bind(&LLAgent::toggleMicrophone, _2), cb_info::UNTRUSTED_BLOCK); enable.add("Agent.IsMicrophoneOn", boost::bind(&LLAgent::isMicrophoneOn, _2)); enable.add("Agent.IsActionAllowed", boost::bind(&LLAgent::isActionAllowed, _2)); - commit.add("Agent.ToggleHearMediaSoundFromAvatar", boost::bind(&LLAgent::toggleHearMediaSoundFromAvatar)); - commit.add("Agent.ToggleHearVoiceFromAvatar", boost::bind(&LLAgent::toggleHearVoiceFromAvatar)); + registrar.add("Agent.ToggleHearMediaSoundFromAvatar", boost::bind(&LLAgent::toggleHearMediaSoundFromAvatar), cb_info::UNTRUSTED_BLOCK); + registrar.add("Agent.ToggleHearVoiceFromAvatar", boost::bind(&LLAgent::toggleHearVoiceFromAvatar), cb_info::UNTRUSTED_BLOCK); // File menu init_menu_file(); -- cgit v1.2.3 From 26efc7e376ef52284a6281f36cf45eb03bc13507 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 Sep 2024 15:25:07 -0400 Subject: Pass std::string_view by value, not by const reference. Consensus seems to be that (a) string_view is, in effect, already a reference, (b) it's small enough to make pass-by-value reasonable and (c) the optimizer can reason about values way better than it can about references. --- indra/llcommon/hexdump.h | 4 ++-- indra/llcommon/llleaplistener.cpp | 2 +- indra/llcommon/llleaplistener.h | 2 +- indra/llcommon/lua_function.cpp | 4 ++-- indra/llcommon/lua_function.h | 8 ++++---- indra/llcommon/tests/llrand_test.cpp | 2 +- indra/newview/llluamanager.cpp | 2 +- indra/newview/tests/llluamanager_test.cpp | 2 +- indra/test/namedtempfile.h | 24 ++++++++++++------------ 9 files changed, 25 insertions(+), 25 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/hexdump.h b/indra/llcommon/hexdump.h index ab5ba2b16d..4b734426a3 100755 --- a/indra/llcommon/hexdump.h +++ b/indra/llcommon/hexdump.h @@ -25,7 +25,7 @@ namespace LL class hexdump { public: - hexdump(const std::string_view& data): + hexdump(std::string_view data): hexdump(data.data(), data.length()) {} @@ -66,7 +66,7 @@ private: class hexmix { public: - hexmix(const std::string_view& data): + hexmix(std::string_view data): mData(data) {} diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp index b81ee66ba9..9742c9e9de 100644 --- a/indra/llcommon/llleaplistener.cpp +++ b/indra/llcommon/llleaplistener.cpp @@ -54,7 +54,7 @@ return features; } -LLLeapListener::LLLeapListener(const std::string_view& caller, const Callback& callback): +LLLeapListener::LLLeapListener(std::string_view caller, const Callback& callback): // Each LEAP plugin has an instance of this listener. Make the command // pump name difficult for other such plugins to guess. LLEventAPI(LLUUID::generateNewID().asString(), diff --git a/indra/llcommon/llleaplistener.h b/indra/llcommon/llleaplistener.h index d36d2ff8db..d38a6f4ace 100644 --- a/indra/llcommon/llleaplistener.h +++ b/indra/llcommon/llleaplistener.h @@ -28,7 +28,7 @@ public: * event is received. */ using Callback = std::function; - LLLeapListener(const std::string_view& caller, const Callback& callback); + LLLeapListener(std::string_view caller, const Callback& callback); ~LLLeapListener(); LLEventPump& getReplyPump() { return mReplyPump; } diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index eefb1e62cf..380e650360 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -999,8 +999,8 @@ LuaPopper::~LuaPopper() /***************************************************************************** * LuaFunction class *****************************************************************************/ -LuaFunction::LuaFunction(const std::string_view& name, lua_CFunction function, - const std::string_view& helptext) +LuaFunction::LuaFunction(std::string_view name, lua_CFunction function, + std::string_view helptext) { const auto& [registry, lookup] = getState(); registry.emplace(name, Registry::mapped_type{ function, helptext }); diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 967d8eaba1..12e74b5d04 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -351,7 +351,7 @@ auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value) // return to C++, from table at index, the value of field k (without metamethods) template -auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) +auto lua_rawgetfield(lua_State* L, int index, std::string_view k) { index = lua_absindex(L, index); lua_checkdelta(L); @@ -364,7 +364,7 @@ auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) // set in table at index, as field k, the specified C++ value (without metamethods) template -void lua_rawsetfield(lua_State* L, int index, const std::string_view& k, const T& value) +void lua_rawsetfield(lua_State* L, int index, std::string_view k, const T& value) { index = lua_absindex(L, index); lua_checkdelta(L); @@ -389,8 +389,8 @@ void lua_rawsetfield(lua_State* L, int index, const std::string_view& k, const T class LuaFunction { public: - LuaFunction(const std::string_view& name, lua_CFunction function, - const std::string_view& helptext); + LuaFunction(std::string_view name, lua_CFunction function, + std::string_view helptext); static void init(lua_State* L); diff --git a/indra/llcommon/tests/llrand_test.cpp b/indra/llcommon/tests/llrand_test.cpp index a0dd4ef576..85dd53ce96 100644 --- a/indra/llcommon/tests/llrand_test.cpp +++ b/indra/llcommon/tests/llrand_test.cpp @@ -38,7 +38,7 @@ // testing extent < 0, negate the return value and the extent before passing // into ensure_in_range(). template -void ensure_in_range(const std::string_view& name, +void ensure_in_range(std::string_view name, NUMBER value, NUMBER low, NUMBER high) { auto failmsg{ stringize(name, " >= ", low, " (", value, ')') }; diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 6a725e785f..0bd21516bc 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -65,7 +65,7 @@ lua_function(sleep, "sleep(seconds): pause the running coroutine") // This function consumes ALL Lua stack arguments and returns concatenated // message string -std::string lua_print_msg(lua_State* L, const std::string_view& level) +std::string lua_print_msg(lua_State* L, std::string_view level) { // On top of existing Lua arguments, we're going to push tostring() and // duplicate each existing stack entry so we can stringize each one. diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index 8d1333815b..f0a1b32eed 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -123,7 +123,7 @@ namespace tut } } - void from_lua(const std::string& desc, const std::string_view& construct, const LLSD& expect) + void from_lua(const std::string& desc, std::string_view construct, const LLSD& expect) { LLSD fromlua; LLStreamListener pump("testpump", diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index 8027f95728..96b19523ab 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -32,18 +32,18 @@ class NamedTempFile: public boost::noncopyable { LOG_CLASS(NamedTempFile); public: - NamedTempFile(const std::string_view& pfx, - const std::string_view& content, - const std::string_view& sfx=std::string_view("")) + NamedTempFile(std::string_view pfx, + std::string_view content, + std::string_view sfx=std::string_view("")) { createFile(pfx, [&content](std::ostream& out){ out << content; }, sfx); } // Disambiguate when passing string literal -- unclear why a string // literal should be ambiguous wrt std::string_view and Streamer - NamedTempFile(const std::string_view& pfx, + NamedTempFile(std::string_view pfx, const char* content, - const std::string_view& sfx=std::string_view("")) + std::string_view sfx=std::string_view("")) { createFile(pfx, [&content](std::ostream& out){ out << content; }, sfx); } @@ -53,9 +53,9 @@ public: // (boost::phoenix::placeholders::arg1 << "the value is " << 17 << '\n') typedef std::function Streamer; - NamedTempFile(const std::string_view& pfx, + NamedTempFile(std::string_view pfx, const Streamer& func, - const std::string_view& sfx=std::string_view("")) + std::string_view sfx=std::string_view("")) { createFile(pfx, func, sfx); } @@ -94,8 +94,8 @@ public: return out; } - static boost::filesystem::path temp_path(const std::string_view& pfx="", - const std::string_view& sfx="") + static boost::filesystem::path temp_path(std::string_view pfx="", + std::string_view sfx="") { // This variable is set by GitHub actions and is the recommended place // to put temp files belonging to an actions job. @@ -114,9 +114,9 @@ public: } protected: - void createFile(const std::string_view& pfx, + void createFile(std::string_view pfx, const Streamer& func, - const std::string_view& sfx) + std::string_view sfx) { // Create file in a temporary place. mPath = temp_path(pfx, sfx); @@ -137,7 +137,7 @@ class NamedExtTempFile: public NamedTempFile { LOG_CLASS(NamedExtTempFile); public: - NamedExtTempFile(const std::string& ext, const std::string_view& content): + NamedExtTempFile(const std::string& ext, std::string_view content): NamedTempFile(remove_dot(ext), content, ensure_dot(ext)) {} -- cgit v1.2.3 From 9f38f25b93be2566399fac2d528da9810edd2fa6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 Sep 2024 15:28:16 -0400 Subject: llinstancetracker.h was missing an #include from last merge. --- indra/llcommon/llinstancetracker.h | 1 + 1 file changed, 1 insertion(+) (limited to 'indra') diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index 03418e9bad..b5c681e60a 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -41,6 +41,7 @@ #include #include +#include "llprofiler.h" #include "lockstatic.h" #include "stringize.h" -- cgit v1.2.3 From dca6f0deae49d133f180ef937939b8648649fbc6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 Sep 2024 17:16:14 -0400 Subject: Fix risky signature of `wchar_to_utf8chars()`. Add `ll_convert()` alias. `wchar_to_utf8chars()` used to require a `char*` output buffer with no length, assuming that its caller knew enough to provide a buffer of sufficient length. In fact a `char[8]` buffer suffices, but nothing in the header indicated that. Eliminate the output parameter and return `std::string`. Fix the few existing callers. Also set an `ll_convert_alias` so that `ll_convert_to(llwchar)` directly calls `wchar_to_utf8chars()`. Replace instances of the workaround `wstring_to_utf8str(LLWString(1, llwchar))`. --- indra/llcommon/llstring.cpp | 20 ++++++++------------ indra/llcommon/llstring.h | 6 +++++- indra/llui/llviewereventrecorder.cpp | 11 +++++------ indra/newview/llpanelemojicomplete.cpp | 3 +-- indra/newview/llviewermedia.cpp | 3 ++- 5 files changed, 21 insertions(+), 22 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 505789f9ea..4d7cf90310 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -141,10 +141,10 @@ std::string rawstr_to_utf8(const std::string& raw) return wstring_to_utf8str(wstr); } -std::ptrdiff_t wchar_to_utf8chars(llwchar in_char, char* outchars) +std::string wchar_to_utf8chars(llwchar in_char) { - U32 cur_char = (U32)in_char; - char* base = outchars; + U32 cur_char(in_char); + char buff[8], *outchars = buff; if (cur_char < 0x80) { *outchars++ = (U8)cur_char; @@ -189,7 +189,7 @@ std::ptrdiff_t wchar_to_utf8chars(llwchar in_char, char* outchars) LL_WARNS() << "Invalid Unicode character " << cur_char << "!" << LL_ENDL; *outchars++ = LL_UNKNOWN_CHAR; } - return outchars - base; + return { buff, std::string::size_type(outchars - buff) }; } auto utf16chars_to_wchar(const U16* inchars, llwchar* outchar) @@ -367,13 +367,12 @@ std::string wchar_utf8_preview(const llwchar wc) std::ostringstream oss; oss << std::hex << std::uppercase << (U32)wc; - U8 out_bytes[8]; - U32 size = (U32)wchar_to_utf8chars(wc, (char*)out_bytes); + auto out_bytes = wchar_to_utf8chars(wc); - if (size > 1) + if (out_bytes.length() > 1) { oss << " ["; - for (U32 i = 0; i < size; ++i) + for (U32 i = 0; i < out_bytes.length(); ++i) { if (i) { @@ -492,10 +491,7 @@ std::string wstring_to_utf8str(const llwchar* utf32str, size_t len) S32 i = 0; while (i < len) { - char tchars[8]; /* Flawfinder: ignore */ - auto n = wchar_to_utf8chars(utf32str[i], tchars); - tchars[n] = 0; - out += tchars; + out += wchar_to_utf8chars(utf32str[i]); i++; } return out; diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index e4be1efaed..b552aede82 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -707,7 +707,11 @@ ll_convert_forms(ll_convert_alias, LLWString, std::string, utf8str_to_ // Same function, better name. JC inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); } -LL_COMMON_API std::ptrdiff_t wchar_to_utf8chars(llwchar inchar, char* outchars); +// return a UTF-8 string representation of a single llwchar, which we +// occasionally require: +// cheaper than ll_convert_to(LLWString(1, inchar)) +LL_COMMON_API std::string wchar_to_utf8chars(llwchar inchar); +ll_convert_alias(std::string, llwchar, wchar_to_utf8chars(in)); ll_convert_forms(ll_convert_alias, std::string, LLWString, wstring_to_utf8str); ll_convert_forms(ll_convert_u16_alias, std::string, llutf16string, utf16str_to_utf8str); diff --git a/indra/llui/llviewereventrecorder.cpp b/indra/llui/llviewereventrecorder.cpp index 6d907d7e45..0a4fe5234b 100644 --- a/indra/llui/llviewereventrecorder.cpp +++ b/indra/llui/llviewereventrecorder.cpp @@ -24,9 +24,10 @@ */ -#include "llviewereventrecorder.h" -#include "llui.h" #include "llleap.h" +#include "llstring.h" +#include "llui.h" +#include "llviewereventrecorder.h" LLViewerEventRecorder::LLViewerEventRecorder() { @@ -247,11 +248,9 @@ void LLViewerEventRecorder::logKeyUnicodeEvent(llwchar uni_char) { // keycode...or // char - LL_DEBUGS() << "Wrapped in conversion to wstring " << wstring_to_utf8str(LLWString( 1, uni_char)) << "\n" << LL_ENDL; + LL_DEBUGS() << "Wrapped in conversion to wstring " << ll_convert_to(uni_char) << "\n" << LL_ENDL; - event.insert("char", - LLSD( wstring_to_utf8str(LLWString( 1,uni_char)) ) - ); + event.insert("char", LLSD(ll_convert_to(uni_char))); // path (optional) - for now we are not recording path for key events during record - should not be needed for full record and playback of recorded steps // as a vita script - it does become useful if you edit the resulting vita script and wish to remove some steps leading to a key event - that sort of edit might diff --git a/indra/newview/llpanelemojicomplete.cpp b/indra/newview/llpanelemojicomplete.cpp index cb89a5910e..7f72677e34 100644 --- a/indra/newview/llpanelemojicomplete.cpp +++ b/indra/newview/llpanelemojicomplete.cpp @@ -280,8 +280,7 @@ void LLPanelEmojiComplete::onCommit() { if (mCurSelected < mTotalEmojis) { - LLSD value(wstring_to_utf8str(LLWString(1, mEmojis[mCurSelected].Character))); - setValue(value); + setValue(ll_convert_to(mEmojis[mCurSelected].Character)); LLUICtrl::onCommit(); } } diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index 9739cac311..1c8f1a32b9 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -2766,7 +2766,8 @@ bool LLViewerMediaImpl::handleUnicodeCharHere(llwchar uni_char) { LLSD native_key_data = gViewerWindow->getWindow()->getNativeKeyData(); - mMediaSource->textInput(wstring_to_utf8str(LLWString(1, uni_char)), gKeyboard->currentMask(false), native_key_data); + mMediaSource->textInput(ll_convert_to(uni_char), + gKeyboard->currentMask(false), native_key_data); } } -- cgit v1.2.3 From 96e061547d15cb44dbe091e7aec507b2d9c87da2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 Sep 2024 17:22:35 -0400 Subject: In llstring.cpp, build result strings using basic_ostringstream. Many of the string conversion functions in llstring.cpp would build their result strings using successive concatenation operations, piece by piece. This can be expensive in allocations. Instead, use a std::basic_ostringstream of char type appropriate to the return string type to aggregate piecewise string building. --- indra/llcommon/llstring.cpp | 100 +++++++++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 42 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 4d7cf90310..614075498c 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -214,7 +214,8 @@ auto utf16chars_to_wchar(const U16* inchars, llwchar* outchar) llutf16string wstring_to_utf16str(const llwchar* utf32str, size_t len) { - llutf16string out; + // ostringstream for llutf16string + std::basic_ostringstream out; S32 i = 0; while (i < len) @@ -222,16 +223,16 @@ llutf16string wstring_to_utf16str(const llwchar* utf32str, size_t len) U32 cur_char = utf32str[i]; if (cur_char > 0xFFFF) { - out += (0xD7C0 + (cur_char >> 10)); - out += (0xDC00 | (cur_char & 0x3FF)); + out.put(U16(0xD7C0 + (cur_char >> 10))); + out.put(U16(0xDC00 | (cur_char & 0x3FF))); } else { - out += cur_char; + out.put(U16(cur_char)); } i++; } - return out; + return out.str(); } llutf16string utf8str_to_utf16str( const char* utf8str, size_t len ) @@ -242,8 +243,10 @@ llutf16string utf8str_to_utf16str( const char* utf8str, size_t len ) LLWString utf16str_to_wstring(const U16* utf16str, size_t len) { - LLWString wout; - if (len == 0) return wout; + if (len == 0) return {}; + + // ostringstream for LLWString + std::basic_ostringstream wout; S32 i = 0; const U16* chars16 = utf16str; @@ -251,9 +254,9 @@ LLWString utf16str_to_wstring(const U16* utf16str, size_t len) { llwchar cur_char; i += (S32)utf16chars_to_wchar(chars16+i, &cur_char); - wout += cur_char; + wout << cur_char; } - return wout; + return wout.str(); } // Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. @@ -398,7 +401,8 @@ S32 wstring_utf8_length(const LLWString& wstr) LLWString utf8str_to_wstring(const char* utf8str, size_t len) { - LLWString wout; + // ostringstream for LLWString + std::basic_ostringstream wout; S32 i = 0; while (i < len) @@ -441,7 +445,7 @@ LLWString utf8str_to_wstring(const char* utf8str, size_t len) } else { - wout += LL_UNKNOWN_CHAR; + wout << LL_UNKNOWN_CHAR; ++i; continue; } @@ -478,23 +482,21 @@ LLWString utf8str_to_wstring(const char* utf8str, size_t len) } } - wout += unichar; + wout << unichar; ++i; } - return wout; + return wout.str(); } std::string wstring_to_utf8str(const llwchar* utf32str, size_t len) { - std::string out; + std::ostringstream out; - S32 i = 0; - while (i < len) + for (size_t i = 0; i < len; ++i) { - out += wchar_to_utf8chars(utf32str[i]); - i++; + out << wchar_to_utf8chars(utf32str[i]); } - return out; + return out.str(); } std::string utf16str_to_utf8str(const U16* utf16str, size_t len) @@ -682,7 +684,21 @@ llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t lengt std::string utf8str_showBytesUTF8(const std::string& utf8str) { - std::string result; + std::ostringstream result; + char lastchar = '\0'; + auto append = [&result, &lastchar](char c) + { + lastchar = c; + result << c; + }; + auto appends = [&result, &lastchar](const std::string& s) + { + if (! s.empty()) + { + lastchar = s.back(); + result << s; + } + }; bool in_sequence = false; size_t sequence_size = 0; @@ -691,9 +707,9 @@ std::string utf8str_showBytesUTF8(const std::string& utf8str) auto open_sequence = [&]() { - if (!result.empty() && result.back() != '\n') - result += '\n'; // Use LF as a separator before new UTF-8 sequence - result += '['; + if (lastchar != '\0' && lastchar != '\n') + append('\n'); // Use LF as a separator before new UTF-8 sequence + append('['); in_sequence = true; }; @@ -702,9 +718,9 @@ std::string utf8str_showBytesUTF8(const std::string& utf8str) llwchar unicode = utf8str_to_wchar(utf8str, byte_index - sequence_size, sequence_size); if (unicode != LL_UNKNOWN_CHAR) { - result += llformat("+%04X", unicode); + appends(llformat("+%04X", unicode)); } - result += ']'; + append(']'); in_sequence = false; sequence_size = 0; }; @@ -725,9 +741,9 @@ std::string utf8str_showBytesUTF8(const std::string& utf8str) } else // Continue the same UTF-8 sequence { - result += '.'; + append('.'); } - result += llformat("%02X", byte); // The byte is represented in hexadecimal form + appends(llformat("%02X", byte)); // The byte is represented in hexadecimal form ++sequence_size; } else // ASCII symbol is represented as a character @@ -737,10 +753,10 @@ std::string utf8str_showBytesUTF8(const std::string& utf8str) close_sequence(); if (byte != '\n') { - result += '\n'; // Use LF as a separator between UTF-8 and ASCII + append('\n'); // Use LF as a separator between UTF-8 and ASCII } } - result += byte; + append(byte); } ++byte_index; } @@ -750,7 +766,7 @@ std::string utf8str_showBytesUTF8(const std::string& utf8str) close_sequence(); } - return result; + return result.str(); } // Search for any emoji symbol, return true if found @@ -1583,7 +1599,7 @@ S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions) LL_PROFILE_ZONE_SCOPED_CATEGORY_STRING; S32 res = 0; - std::string output; + std::ostringstream output; std::vector tokens; std::string::size_type start = 0; @@ -1591,7 +1607,7 @@ S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions) std::string::size_type key_start = 0; while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos) { - output += std::string(s, prev_start, key_start-prev_start); + output << std::string(s, prev_start, key_start-prev_start); prev_start = start; bool found_replacement = false; @@ -1632,20 +1648,20 @@ S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions) if (found_replacement) { - output += replacement; + output << replacement; res++; } else { // we had no replacement, use the string as is // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-" - output += std::string(s, key_start, start-key_start); + output << std::string(s, key_start, start-key_start); } tokens.clear(); } // send the remainder of the string (with no further matches for bracketed names) - output += std::string(s, start); - s = output; + output << std::string(s, start); + s = output.str(); return res; } @@ -1661,7 +1677,7 @@ S32 LLStringUtil::format(std::string& s, const LLSD& substitutions) return res; } - std::string output; + std::ostringstream output; std::vector tokens; std::string::size_type start = 0; @@ -1669,7 +1685,7 @@ S32 LLStringUtil::format(std::string& s, const LLSD& substitutions) std::string::size_type key_start = 0; while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos) { - output += std::string(s, prev_start, key_start-prev_start); + output << std::string(s, prev_start, key_start-prev_start); prev_start = start; bool found_replacement = false; @@ -1702,20 +1718,20 @@ S32 LLStringUtil::format(std::string& s, const LLSD& substitutions) if (found_replacement) { - output += replacement; + output << replacement; res++; } else { // we had no replacement, use the string as is // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-" - output += std::string(s, key_start, start-key_start); + output << std::string(s, key_start, start-key_start); } tokens.clear(); } // send the remainder of the string (with no further matches for bracketed names) - output += std::string(s, start); - s = output; + output << std::string(s, start); + s = output.str(); return res; } -- cgit v1.2.3 From d5712689d36a1ee1af32242706901fde7229b08d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 11 Sep 2024 12:29:18 -0400 Subject: Make Develop->Render Tests->Frame Profile dump JSON to a file too. Make `LLGLSLShader::finishProfile()` accept a string pathname instead of a bool and, in addition to logging statistics to the viewer log, output statistics to that file as JSON. The calls that used to pass `emit_report=false` now pass `report_name=std::string()`. Make llviewerdisplay.cpp's `display()` function synthesize a profile filename in the viewer's logs directory, and pass that filename to `LLGLSLShader::finishProfile()`. --- indra/llrender/llglslshader.cpp | 120 +++++++++++++++++++++++-------------- indra/llrender/llglslshader.h | 5 +- indra/newview/llfeaturemanager.cpp | 2 +- indra/newview/llglsandbox.cpp | 2 +- indra/newview/llviewerdisplay.cpp | 102 +++++++++++++++++++++++-------- 5 files changed, 158 insertions(+), 73 deletions(-) (limited to 'indra') diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index a157bfee21..bb734971d5 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -41,6 +41,8 @@ #include "OpenGL/OpenGL.h" #endif +#include + // Print-print list of shader included source files that are linked together via glAttachShader() // i.e. On macOS / OSX the AMD GLSL linker will display an error if a varying is left in an undefined state. #define DEBUG_SHADER_INCLUDES 0 @@ -101,9 +103,9 @@ void LLGLSLShader::initProfile() sTotalSamplesDrawn = 0; sTotalBinds = 0; - for (std::set::iterator iter = sInstances.begin(); iter != sInstances.end(); ++iter) + for (auto ptr : sInstances) { - (*iter)->clearStats(); + ptr->clearStats(); } } @@ -117,48 +119,71 @@ struct LLGLSLShaderCompareTimeElapsed }; //static -void LLGLSLShader::finishProfile(bool emit_report) +void LLGLSLShader::finishProfile(const std::string& report_name) { sProfileEnabled = false; - if (emit_report) + if (! report_name.empty()) { - std::vector sorted; - - for (std::set::iterator iter = sInstances.begin(); iter != sInstances.end(); ++iter) - { - sorted.push_back(*iter); - } - + std::vector sorted(sInstances.begin(), sInstances.end()); std::sort(sorted.begin(), sorted.end(), LLGLSLShaderCompareTimeElapsed()); + boost::json::object stats; + auto shadersit = stats.emplace("shaders", boost::json::array_kind).first; + auto& shaders = shadersit->value().as_array(); bool unbound = false; - for (std::vector::iterator iter = sorted.begin(); iter != sorted.end(); ++iter) + for (auto ptr : sorted) { - (*iter)->dumpStats(); - if ((*iter)->mBinds == 0) + if (ptr->mBinds == 0) { unbound = true; } + else + { + auto& shaderit = shaders.emplace_back(boost::json::object_kind); + ptr->dumpStats(shaderit.as_object()); + } } + constexpr float mega = 1'000'000.f; + float totalTimeMs = sTotalTimeElapsed / mega; LL_INFOS() << "-----------------------------------" << LL_ENDL; - LL_INFOS() << "Total rendering time: " << llformat("%.4f ms", sTotalTimeElapsed / 1000000.f) << LL_ENDL; - LL_INFOS() << "Total samples drawn: " << llformat("%.4f million", sTotalSamplesDrawn / 1000000.f) << LL_ENDL; - LL_INFOS() << "Total triangles drawn: " << llformat("%.3f million", sTotalTrianglesDrawn / 1000000.f) << LL_ENDL; + LL_INFOS() << "Total rendering time: " << llformat("%.4f ms", totalTimeMs) << LL_ENDL; + LL_INFOS() << "Total samples drawn: " << llformat("%.4f million", sTotalSamplesDrawn / mega) << LL_ENDL; + LL_INFOS() << "Total triangles drawn: " << llformat("%.3f million", sTotalTrianglesDrawn / mega) << LL_ENDL; LL_INFOS() << "-----------------------------------" << LL_ENDL; - + auto totalsit = stats.emplace("totals", boost::json::object_kind).first; + auto& totals = totalsit->value().as_object(); + totals.emplace("time", totalTimeMs / 1000.0); + totals.emplace("binds", sTotalBinds); + totals.emplace("samples", sTotalSamplesDrawn); + totals.emplace("triangles", sTotalTrianglesDrawn); + + auto unusedit = stats.emplace("unused", boost::json::array_kind).first; + auto& unused = unusedit->value().as_array(); if (unbound) { LL_INFOS() << "The following shaders were unused: " << LL_ENDL; - for (std::vector::iterator iter = sorted.begin(); iter != sorted.end(); ++iter) + for (auto ptr : sorted) { - if ((*iter)->mBinds == 0) + if (ptr->mBinds == 0) { - LL_INFOS() << (*iter)->mName << LL_ENDL; + LL_INFOS() << ptr->mName << LL_ENDL; + unused.emplace_back(ptr->mName); } } } + + std::ofstream outf(report_name); + if (! outf) + { + LL_WARNS() << "Couldn't write to " << std::quoted(report_name) << LL_ENDL; + } + else + { + outf << stats; + LL_INFOS() << "(also dumped to " << std::quoted(report_name) << ")" << LL_ENDL; + } } } @@ -170,36 +195,43 @@ void LLGLSLShader::clearStats() mBinds = 0; } -void LLGLSLShader::dumpStats() +void LLGLSLShader::dumpStats(boost::json::object& stats) { - if (mBinds > 0) + stats.emplace("name", mName); + auto filesit = stats.emplace("files", boost::json::array_kind).first; + auto& files = filesit->value().as_array(); + LL_INFOS() << "=============================================" << LL_ENDL; + LL_INFOS() << mName << LL_ENDL; + for (U32 i = 0; i < mShaderFiles.size(); ++i) { - LL_INFOS() << "=============================================" << LL_ENDL; - LL_INFOS() << mName << LL_ENDL; - for (U32 i = 0; i < mShaderFiles.size(); ++i) - { - LL_INFOS() << mShaderFiles[i].first << LL_ENDL; - } - LL_INFOS() << "=============================================" << LL_ENDL; + LL_INFOS() << mShaderFiles[i].first << LL_ENDL; + files.emplace_back(mShaderFiles[i].first); + } + LL_INFOS() << "=============================================" << LL_ENDL; - F32 ms = mTimeElapsed / 1000000.f; - F32 seconds = ms / 1000.f; + constexpr float mega = 1'000'000.f; + constexpr double giga = 1'000'000'000.0; + F32 ms = mTimeElapsed / mega; + F32 seconds = ms / 1000.f; - F32 pct_tris = (F32)mTrianglesDrawn / (F32)sTotalTrianglesDrawn * 100.f; - F32 tris_sec = (F32)(mTrianglesDrawn / 1000000.0); - tris_sec /= seconds; + F32 pct_tris = (F32)mTrianglesDrawn / (F32)sTotalTrianglesDrawn * 100.f; + F32 tris_sec = (F32)(mTrianglesDrawn / mega); + tris_sec /= seconds; - F32 pct_samples = (F32)((F64)mSamplesDrawn / (F64)sTotalSamplesDrawn) * 100.f; - F32 samples_sec = (F32)(mSamplesDrawn / 1000000000.0); - samples_sec /= seconds; + F32 pct_samples = (F32)((F64)mSamplesDrawn / (F64)sTotalSamplesDrawn) * 100.f; + F32 samples_sec = (F32)(mSamplesDrawn / giga); + samples_sec /= seconds; - F32 pct_binds = (F32)mBinds / (F32)sTotalBinds * 100.f; + F32 pct_binds = (F32)mBinds / (F32)sTotalBinds * 100.f; - LL_INFOS() << "Triangles Drawn: " << mTrianglesDrawn << " " << llformat("(%.2f pct of total, %.3f million/sec)", pct_tris, tris_sec) << LL_ENDL; - LL_INFOS() << "Binds: " << mBinds << " " << llformat("(%.2f pct of total)", pct_binds) << LL_ENDL; - LL_INFOS() << "SamplesDrawn: " << mSamplesDrawn << " " << llformat("(%.2f pct of total, %.3f billion/sec)", pct_samples, samples_sec) << LL_ENDL; - LL_INFOS() << "Time Elapsed: " << mTimeElapsed << " " << llformat("(%.2f pct of total, %.5f ms)\n", (F32)((F64)mTimeElapsed / (F64)sTotalTimeElapsed) * 100.f, ms) << LL_ENDL; - } + LL_INFOS() << "Triangles Drawn: " << mTrianglesDrawn << " " << llformat("(%.2f pct of total, %.3f million/sec)", pct_tris, tris_sec) << LL_ENDL; + LL_INFOS() << "Binds: " << mBinds << " " << llformat("(%.2f pct of total)", pct_binds) << LL_ENDL; + LL_INFOS() << "SamplesDrawn: " << mSamplesDrawn << " " << llformat("(%.2f pct of total, %.3f billion/sec)", pct_samples, samples_sec) << LL_ENDL; + LL_INFOS() << "Time Elapsed: " << mTimeElapsed << " " << llformat("(%.2f pct of total, %.5f ms)\n", (F32)((F64)mTimeElapsed / (F64)sTotalTimeElapsed) * 100.f, ms) << LL_ENDL; + stats.emplace("time", seconds); + stats.emplace("binds", mBinds); + stats.emplace("samples", mSamplesDrawn); + stats.emplace("triangles", mTrianglesDrawn); } //static diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h index 27c8f0b7d0..efcbaf42e8 100644 --- a/indra/llrender/llglslshader.h +++ b/indra/llrender/llglslshader.h @@ -30,6 +30,7 @@ #include "llgl.h" #include "llrender.h" #include "llstaticstringtable.h" +#include #include class LLShaderFeatures @@ -169,14 +170,14 @@ public: static U32 sMaxGLTFNodes; static void initProfile(); - static void finishProfile(bool emit_report = true); + static void finishProfile(const std::string& report_name={}); static void startProfile(); static void stopProfile(); void unload(); void clearStats(); - void dumpStats(); + void dumpStats(boost::json::object& stats); // place query objects for profiling if profiling is enabled // if for_runtime is true, will place timer query only whether or not profiling is enabled diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp index aa04221f4b..dded4d1e92 100644 --- a/indra/newview/llfeaturemanager.cpp +++ b/indra/newview/llfeaturemanager.cpp @@ -393,7 +393,7 @@ F32 logExceptionBenchmark() __except (msc_exception_filter(GetExceptionCode(), GetExceptionInformation())) { // HACK - ensure that profiling is disabled - LLGLSLShader::finishProfile(false); + LLGLSLShader::finishProfile(); // convert to C++ styled exception char integer_string[32]; diff --git a/indra/newview/llglsandbox.cpp b/indra/newview/llglsandbox.cpp index 930a8c28d9..c932312135 100644 --- a/indra/newview/llglsandbox.cpp +++ b/indra/newview/llglsandbox.cpp @@ -923,7 +923,7 @@ struct ShaderProfileHelper } ~ShaderProfileHelper() { - LLGLSLShader::finishProfile(false); + LLGLSLShader::finishProfile(); } }; diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index 9bd0973cc0..c62702e820 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -28,58 +28,69 @@ #include "llviewerdisplay.h" -#include "llgl.h" -#include "llrender.h" -#include "llglheaders.h" -#include "llgltfmateriallist.h" +#include "fsyspath.h" +#include "hexdump.h" #include "llagent.h" #include "llagentcamera.h" -#include "llviewercontrol.h" +#include "llappviewer.h" #include "llcoord.h" #include "llcriticaldamp.h" +#include "llcubemap.h" #include "lldir.h" -#include "lldynamictexture.h" #include "lldrawpoolalpha.h" +#include "lldrawpoolbump.h" +#include "lldrawpoolwater.h" +#include "lldynamictexture.h" +#include "llenvironment.h" +#include "llfasttimer.h" #include "llfeaturemanager.h" -//#include "llfirstuse.h" +#include "llfloatertools.h" +#include "llfocusmgr.h" +#include "llgl.h" +#include "llglheaders.h" +#include "llgltfmateriallist.h" #include "llhudmanager.h" #include "llimagepng.h" +#include "llmachineid.h" #include "llmemory.h" +#include "llparcel.h" +#include "llperfstats.h" +#include "llpostprocess.h" +#include "llrender.h" +#include "llscenemonitor.h" #include "llselectmgr.h" #include "llsky.h" +#include "llspatialpartition.h" #include "llstartup.h" +#include "llstartup.h" +#include "lltooldraganddrop.h" #include "lltoolfocus.h" #include "lltoolmgr.h" -#include "lltooldraganddrop.h" #include "lltoolpie.h" #include "lltracker.h" #include "lltrans.h" #include "llui.h" +#include "lluuid.h" +#include "llversioninfo.h" #include "llviewercamera.h" +#include "llviewercontrol.h" +#include "llviewernetwork.h" #include "llviewerobjectlist.h" #include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewershadermgr.h" +#include "llviewertexturelist.h" #include "llviewerwindow.h" #include "llvoavatarself.h" #include "llvograss.h" #include "llworld.h" #include "pipeline.h" -#include "llspatialpartition.h" -#include "llappviewer.h" -#include "llstartup.h" -#include "llviewershadermgr.h" -#include "llfasttimer.h" -#include "llfloatertools.h" -#include "llviewertexturelist.h" -#include "llfocusmgr.h" -#include "llcubemap.h" -#include "llviewerregion.h" -#include "lldrawpoolwater.h" -#include "lldrawpoolbump.h" -#include "llpostprocess.h" -#include "llscenemonitor.h" -#include "llenvironment.h" -#include "llperfstats.h" +#include + +#include +#include +#include extern LLPointer gStartTexture; extern bool gShiftFrame; @@ -123,6 +134,8 @@ void render_ui_3d(); void render_ui_2d(); void render_disconnected_background(); +std::string getProfileStatsFilename(); + void display_startup() { if ( !gViewerWindow @@ -1023,10 +1036,49 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) if (gShaderProfileFrame) { gShaderProfileFrame = false; - LLGLSLShader::finishProfile(); + LLGLSLShader::finishProfile(getProfileStatsFilename()); } } +std::string getProfileStatsFilename() +{ + std::ostringstream basebuff; + // viewer build + basebuff << "profile.v" << LLVersionInfo::instance().getBuild(); + // machine ID: zero-initialize unique_id in case LLMachineID fails + unsigned char unique_id[MAC_ADDRESS_BYTES]{}; + LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); + basebuff << ".m" << LL::hexdump(unique_id, sizeof(unique_id)); + // region ID + LLViewerRegion *region = gAgent.getRegion(); + basebuff << ".r" << (region? region->getRegionID() : LLUUID()); + // local parcel ID + LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); + basebuff << ".p" << (parcel? parcel->getLocalID() : 0); + // date/time -- omit seconds for now + auto now = LLDate::now(); + basebuff << ".t" << LLDate::now().toHTTPDateString("%Y-%m-%dT%H-%M-"); + // put this candidate file in our logs directory + auto base = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, basebuff.str()); + S32 sec; + now.split(nullptr, nullptr, nullptr, nullptr, nullptr, &sec); + // Loop over finished filename, incrementing sec until we find one that + // doesn't yet exist. Should rarely loop (only if successive calls within + // same second), may produce (e.g.) sec==61, but avoids collisions and + // preserves chronological filename sort order. + std::string name; + std::error_code ec; + do + { + // base + missing 2-digit seconds, append ".json" + // post-increment sec in case we have to try again + name = stringize(base, std::setw(2), std::setfill('0'), sec++, ".json"); + } while (std::filesystem::exists(fsyspath(name), ec)); + // Ignoring ec means we might potentially return a name that does already + // exist -- but if we can't check its existence, what more can we do? + return name; +} + // WIP simplified copy of display() that does minimal work void display_cube_face() { -- cgit v1.2.3 From c6e6f44f50b4de391000c5b9f781a2f0a5024e76 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 09:12:33 -0400 Subject: Give `LLGLSLShader::finishProfile()` a static default string param. `finishProfile()` is called at least once within a `__try` block. If we default its `report_name` parameter to a temporary `std::string`, that temporary must be destroyed when the stack is unwound, which `__try` forbids. --- indra/llrender/llglslshader.cpp | 1 + indra/llrender/llglslshader.h | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index bb734971d5..56f1533708 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -65,6 +65,7 @@ U64 LLGLSLShader::sTotalTimeElapsed = 0; U32 LLGLSLShader::sTotalTrianglesDrawn = 0; U64 LLGLSLShader::sTotalSamplesDrawn = 0; U32 LLGLSLShader::sTotalBinds = 0; +std::string LLGLSLShader::sDefaultReportName; //UI shader -- declared here so llui_libtest will link properly LLGLSLShader gUIProgram; diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h index efcbaf42e8..a9b9bfafa8 100644 --- a/indra/llrender/llglslshader.h +++ b/indra/llrender/llglslshader.h @@ -170,7 +170,7 @@ public: static U32 sMaxGLTFNodes; static void initProfile(); - static void finishProfile(const std::string& report_name={}); + static void finishProfile(const std::string& report_name=sDefaultReportName); static void startProfile(); static void stopProfile(); @@ -364,6 +364,11 @@ public: private: void unloadInternal(); + // This must be static because finishProfile() is called at least once + // within a __try block. If we default its report_name parameter to a + // temporary std::string, that temporary must be destroyed when the stack + // is unwound, which __try forbids. + static std::string sDefaultReportName; }; //UI shader (declared here so llui_libtest will link properly) -- cgit v1.2.3 From 0c1b3c8a38542b5aab10527cf411b57642b1f70f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 09:24:20 -0400 Subject: Specialize `std::numpunct` to fix broken MS `basic_ostream`. MSVC's `std::basic_ostream` template is not implemented in a general way: it can only be instantiated for certain specific `CHAR` types. Declaring a `std::basic_ostringstream` fails on MSVC with C2941. The ugly workaround from Stack Overflow is to clone-and-edit Microsoft's `std::numpunct` template, locally specializing it for the desired `CHAR` type. --- indra/llcommon/llstring.cpp | 158 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) (limited to 'indra') diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 614075498c..23b12bb98d 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -31,10 +31,168 @@ #include "llfasttimer.h" #include "llsd.h" #include +#include #if LL_WINDOWS #include "llwin32headerslean.h" #include // for WideCharToMultiByte + +// From https://stackoverflow.com/a/48716488: +// how to work around MSVC's broken implementation of std::basic_ostream +// (get C2941 otherwise) +// The problem is that the MSVC implementation doesn't generalize for +// arbitrary CHAR. +namespace std +{ + +// The std::numpunct<_Elem> template on which this specialization is based was +// copied 2024-09-11 from xlocnum header with versions: +// FRAMEWORK40VERSION="v4.0" +// FRAMEWORKVERSION="v4.0.30319" +// FRAMEWORKVERSION64="v4.0.30319" +// UCRTVERSION="10.0.22621.0" +// VCTOOLSVERSION="14.40.33807" +// VISUALSTUDIOVERSION="17.0" +// WINDOWSSDKLIBVERSION="10.0.22621.0" +// WINDOWSSDKVERSION="10.0.22621.0" +// Not sure which of the above versions tracks changes to xlocnum. +template <> +class numpunct : public locale::facet // facet for defining numeric punctuation text +{ +private: + friend _Tidy_guard; + +public: + using string_type = basic_string, allocator>; + using char_type = llwchar; + + static locale::id id; // unique facet id + + llwchar decimal_point() const { + return do_decimal_point(); + } + + llwchar thousands_sep() const { + return do_thousands_sep(); + } + + string grouping() const { + return do_grouping(); + } + + string_type falsename() const { + return do_falsename(); + } + + string_type truename() const { + return do_truename(); + } + + explicit numpunct(size_t _Refs = 0) : locale::facet(_Refs) { // construct from current locale + _BEGIN_LOCINFO(_Lobj) + _Init(_Lobj); + if (_Kseparator == 0) { + _Kseparator = // NB: differs from "C" locale + _Maklocchr(',', static_cast(nullptr), _Lobj._Getcvt()); + } + _END_LOCINFO() + } + + numpunct(const _Locinfo& _Lobj, size_t _Refs = 0, bool _Isdef = false) : locale::facet(_Refs) { + _Init(_Lobj, _Isdef); + } + + static size_t _Getcat(const locale::facet** _Ppf = nullptr, const locale* _Ploc = nullptr) { + // return locale category mask and construct standard facet + if (_Ppf && !*_Ppf) { + *_Ppf = new numpunct(_Locinfo(_Ploc->_C_str()), 0, true); + } + return _X_NUMERIC; + } + +protected: + __CLR_OR_THIS_CALL ~numpunct() noexcept override { + _Tidy(); + } + + numpunct(const char* _Locname, size_t _Refs = 0, bool _Isdef = false) : locale::facet(_Refs) { + _BEGIN_LOCINFO(_Lobj(_Locname)) + _Init(_Lobj, _Isdef); + _END_LOCINFO() + } + + template + void _Getvals(_Elem2, const lconv* _Ptr, _Locinfo::_Cvtvec _Cvt) { // get values + _Dp = _Maklocchr(_Ptr->decimal_point[0], static_cast<_Elem2*>(nullptr), _Cvt); + _Kseparator = _Maklocchr(_Ptr->thousands_sep[0], static_cast<_Elem2*>(nullptr), _Cvt); + } + + void _Getvals(wchar_t, const lconv* _Ptr, _Locinfo::_Cvtvec) { // get values + _Dp = static_cast(_Ptr->_W_decimal_point[0]); + _Kseparator = static_cast(_Ptr->_W_thousands_sep[0]); + } + + void _Init(const _Locinfo& _Lobj, bool _Isdef = false) { // initialize from _Lobj + const lconv* _Ptr = _Lobj._Getlconv(); + _Locinfo::_Cvtvec _Cvt = _Lobj._Getcvt(); // conversion information + + _Grouping = nullptr; + _Falsename = nullptr; + _Truename = nullptr; + + _Tidy_guard _Guard{this}; + _Grouping = _Maklocstr(_Isdef ? "" : _Ptr->grouping, static_cast(nullptr), _Lobj._Getcvt()); + _Falsename = _Maklocstr(_Lobj._Getfalse(), static_cast(nullptr), _Cvt); + _Truename = _Maklocstr(_Lobj._Gettrue(), static_cast(nullptr), _Cvt); + _Guard._Target = nullptr; + + if (_Isdef) { // apply defaults for required facets + // _Grouping = _Maklocstr("", static_cast(nullptr), _Cvt); + _Dp = _Maklocchr('.', static_cast(nullptr), _Cvt); + _Kseparator = _Maklocchr(',', static_cast(nullptr), _Cvt); + } else { + _Getvals(llwchar{}, _Ptr, _Cvt); + } + } + + virtual llwchar __CLR_OR_THIS_CALL do_decimal_point() const { + return _Dp; + } + + virtual llwchar __CLR_OR_THIS_CALL do_thousands_sep() const { + return _Kseparator; + } + + virtual string __CLR_OR_THIS_CALL do_grouping() const { + return string{_Grouping}; + } + + virtual string_type __CLR_OR_THIS_CALL do_falsename() const { + return string_type{_Falsename}; + } + + virtual string_type __CLR_OR_THIS_CALL do_truename() const { + return string_type{_Truename}; + } + +private: + void _Tidy() noexcept { // free all storage + _CSTD free(const_cast(_Grouping)); + _CSTD free(const_cast(_Falsename)); + _CSTD free(const_cast(_Truename)); + } + + const char* _Grouping; // grouping string, "" for "C" locale + llwchar _Dp; // decimal point, '.' for "C" locale + llwchar _Kseparator; // thousands separator, '\0' for "C" locale + const llwchar* _Falsename; // name for false, "false" for "C" locale + const llwchar* _Truename; // name for true, "true" for "C" locale +}; + +locale::id numpunct::id; + +} // namespace std + #endif std::string ll_safe_string(const char* in) -- cgit v1.2.3 From b898276a4f480eb5cf3fbc27bea01e2f4a261374 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 09:39:06 -0400 Subject: Work around broken MS `std::basic_ostream`. MSVC's `std::basic_ostream` template is not implemented in a general way: it can only be instantiated for certain specific `CHAR` types. Declaring a `std::basic_ostringstream` fails on MSVC with C2941. Fortunately both llstring.cpp functions that build a `LLWString` incrementally have the same characteristics: (a) they each build it one character at a time, and (b) the length of the result `LLWString` won't exceed the known length of the input string. So it works to declare a `std::vector`, `reserve()` the input length and `push_back()` individual characters. Then we can use `LLWString`'s range constructor to immediately allocate the right size. --- indra/llcommon/llstring.cpp | 187 +++++--------------------------------------- 1 file changed, 21 insertions(+), 166 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 23b12bb98d..005864a843 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -36,163 +36,6 @@ #if LL_WINDOWS #include "llwin32headerslean.h" #include // for WideCharToMultiByte - -// From https://stackoverflow.com/a/48716488: -// how to work around MSVC's broken implementation of std::basic_ostream -// (get C2941 otherwise) -// The problem is that the MSVC implementation doesn't generalize for -// arbitrary CHAR. -namespace std -{ - -// The std::numpunct<_Elem> template on which this specialization is based was -// copied 2024-09-11 from xlocnum header with versions: -// FRAMEWORK40VERSION="v4.0" -// FRAMEWORKVERSION="v4.0.30319" -// FRAMEWORKVERSION64="v4.0.30319" -// UCRTVERSION="10.0.22621.0" -// VCTOOLSVERSION="14.40.33807" -// VISUALSTUDIOVERSION="17.0" -// WINDOWSSDKLIBVERSION="10.0.22621.0" -// WINDOWSSDKVERSION="10.0.22621.0" -// Not sure which of the above versions tracks changes to xlocnum. -template <> -class numpunct : public locale::facet // facet for defining numeric punctuation text -{ -private: - friend _Tidy_guard; - -public: - using string_type = basic_string, allocator>; - using char_type = llwchar; - - static locale::id id; // unique facet id - - llwchar decimal_point() const { - return do_decimal_point(); - } - - llwchar thousands_sep() const { - return do_thousands_sep(); - } - - string grouping() const { - return do_grouping(); - } - - string_type falsename() const { - return do_falsename(); - } - - string_type truename() const { - return do_truename(); - } - - explicit numpunct(size_t _Refs = 0) : locale::facet(_Refs) { // construct from current locale - _BEGIN_LOCINFO(_Lobj) - _Init(_Lobj); - if (_Kseparator == 0) { - _Kseparator = // NB: differs from "C" locale - _Maklocchr(',', static_cast(nullptr), _Lobj._Getcvt()); - } - _END_LOCINFO() - } - - numpunct(const _Locinfo& _Lobj, size_t _Refs = 0, bool _Isdef = false) : locale::facet(_Refs) { - _Init(_Lobj, _Isdef); - } - - static size_t _Getcat(const locale::facet** _Ppf = nullptr, const locale* _Ploc = nullptr) { - // return locale category mask and construct standard facet - if (_Ppf && !*_Ppf) { - *_Ppf = new numpunct(_Locinfo(_Ploc->_C_str()), 0, true); - } - return _X_NUMERIC; - } - -protected: - __CLR_OR_THIS_CALL ~numpunct() noexcept override { - _Tidy(); - } - - numpunct(const char* _Locname, size_t _Refs = 0, bool _Isdef = false) : locale::facet(_Refs) { - _BEGIN_LOCINFO(_Lobj(_Locname)) - _Init(_Lobj, _Isdef); - _END_LOCINFO() - } - - template - void _Getvals(_Elem2, const lconv* _Ptr, _Locinfo::_Cvtvec _Cvt) { // get values - _Dp = _Maklocchr(_Ptr->decimal_point[0], static_cast<_Elem2*>(nullptr), _Cvt); - _Kseparator = _Maklocchr(_Ptr->thousands_sep[0], static_cast<_Elem2*>(nullptr), _Cvt); - } - - void _Getvals(wchar_t, const lconv* _Ptr, _Locinfo::_Cvtvec) { // get values - _Dp = static_cast(_Ptr->_W_decimal_point[0]); - _Kseparator = static_cast(_Ptr->_W_thousands_sep[0]); - } - - void _Init(const _Locinfo& _Lobj, bool _Isdef = false) { // initialize from _Lobj - const lconv* _Ptr = _Lobj._Getlconv(); - _Locinfo::_Cvtvec _Cvt = _Lobj._Getcvt(); // conversion information - - _Grouping = nullptr; - _Falsename = nullptr; - _Truename = nullptr; - - _Tidy_guard _Guard{this}; - _Grouping = _Maklocstr(_Isdef ? "" : _Ptr->grouping, static_cast(nullptr), _Lobj._Getcvt()); - _Falsename = _Maklocstr(_Lobj._Getfalse(), static_cast(nullptr), _Cvt); - _Truename = _Maklocstr(_Lobj._Gettrue(), static_cast(nullptr), _Cvt); - _Guard._Target = nullptr; - - if (_Isdef) { // apply defaults for required facets - // _Grouping = _Maklocstr("", static_cast(nullptr), _Cvt); - _Dp = _Maklocchr('.', static_cast(nullptr), _Cvt); - _Kseparator = _Maklocchr(',', static_cast(nullptr), _Cvt); - } else { - _Getvals(llwchar{}, _Ptr, _Cvt); - } - } - - virtual llwchar __CLR_OR_THIS_CALL do_decimal_point() const { - return _Dp; - } - - virtual llwchar __CLR_OR_THIS_CALL do_thousands_sep() const { - return _Kseparator; - } - - virtual string __CLR_OR_THIS_CALL do_grouping() const { - return string{_Grouping}; - } - - virtual string_type __CLR_OR_THIS_CALL do_falsename() const { - return string_type{_Falsename}; - } - - virtual string_type __CLR_OR_THIS_CALL do_truename() const { - return string_type{_Truename}; - } - -private: - void _Tidy() noexcept { // free all storage - _CSTD free(const_cast(_Grouping)); - _CSTD free(const_cast(_Falsename)); - _CSTD free(const_cast(_Truename)); - } - - const char* _Grouping; // grouping string, "" for "C" locale - llwchar _Dp; // decimal point, '.' for "C" locale - llwchar _Kseparator; // thousands separator, '\0' for "C" locale - const llwchar* _Falsename; // name for false, "false" for "C" locale - const llwchar* _Truename; // name for true, "true" for "C" locale -}; - -locale::id numpunct::id; - -} // namespace std - #endif std::string ll_safe_string(const char* in) @@ -403,8 +246,14 @@ LLWString utf16str_to_wstring(const U16* utf16str, size_t len) { if (len == 0) return {}; - // ostringstream for LLWString - std::basic_ostringstream wout; + // MS doesn't support std::basic_ostringstream; have to work + // around it. + std::vector wout; + // We want to minimize allocations. We don't know how many llwchars we'll + // generate from this utf16str, but we do know the length should be at + // most len. So if we reserve 'len' llwchars, we shouldn't need to expand + // wout incrementally. + wout.reserve(len); S32 i = 0; const U16* chars16 = utf16str; @@ -412,9 +261,9 @@ LLWString utf16str_to_wstring(const U16* utf16str, size_t len) { llwchar cur_char; i += (S32)utf16chars_to_wchar(chars16+i, &cur_char); - wout << cur_char; + wout.push_back(cur_char); } - return wout.str(); + return { wout.begin(), wout.end() }; } // Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. @@ -559,8 +408,14 @@ S32 wstring_utf8_length(const LLWString& wstr) LLWString utf8str_to_wstring(const char* utf8str, size_t len) { - // ostringstream for LLWString - std::basic_ostringstream wout; + // MS doesn't support std::basic_ostringstream; have to work + // around it. + std::vector wout; + // We want to minimize allocations. We don't know how many llwchars we'll + // generate from this utf8str, but we do know the length should be at most + // len. So if we reserve 'len' llwchars, we shouldn't need to expand wout + // incrementally. + wout.reserve(len); S32 i = 0; while (i < len) @@ -603,7 +458,7 @@ LLWString utf8str_to_wstring(const char* utf8str, size_t len) } else { - wout << LL_UNKNOWN_CHAR; + wout.push_back(LL_UNKNOWN_CHAR); ++i; continue; } @@ -640,10 +495,10 @@ LLWString utf8str_to_wstring(const char* utf8str, size_t len) } } - wout << unichar; + wout.push_back(unichar); ++i; } - return wout.str(); + return { wout.begin(), wout.end() }; } std::string wstring_to_utf8str(const llwchar* utf32str, size_t len) -- cgit v1.2.3 From f789bf05053c99d550e6d95e9a512c984ea43ed5 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 10:39:05 -0400 Subject: Populate the viewer package's lua/auto subdir as well as require. --- indra/newview/viewer_manifest.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index aa2b0b0e25..c403b57813 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -169,8 +169,9 @@ class ViewerManifest(LLManifest): with self.prefix(src_dst="scripts/lua"): self.path("*.lua") self.path("*.xml") - with self.prefix(src_dst='require'): - self.path("*.lua") + for subdir in 'require', 'auto': + with self.prefix(src_dst=subdir): + self.path("*.lua") #build_data.json. Standard with exception handling is fine. If we can't open a new file for writing, we have worse problems #platform is computed above with other arg parsing -- cgit v1.2.3 From 034d13bcd77c3cbba00da1ef6c3c59d22f4a689e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 10:39:26 -0400 Subject: Disable happy-path destructor semantics when unwinding C++ stack. If the C++ runtime is already handling an exception, don't try to launch more Lua operations. --- indra/llcommon/lua_function.cpp | 8 +++++++- indra/llcommon/lua_function.h | 5 ++++- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 380e650360..2557fd0cc9 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -709,6 +709,11 @@ int lua_metaipair(lua_State* L) LuaState::~LuaState() { + // If we're unwinding the stack due to an exception, don't bother trying + // to call any callbacks -- either Lua or C++. + if (std::uncaught_exceptions() != 0) + return; + /*---------------------------- feature flag ----------------------------*/ if (mFeature) /*---------------------------- feature flag ----------------------------*/ @@ -990,7 +995,8 @@ lua_function(atexit, "atexit(function): " *****************************************************************************/ LuaPopper::~LuaPopper() { - if (mCount) + // If we're unwinding the C++ stack due to an exception, don't pop! + if (std::uncaught_exceptions() == 0 && mCount) { lua_pop(mState, mCount); } diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 12e74b5d04..10c201c234 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -172,7 +172,10 @@ public: LuaRemover& operator=(const LuaRemover&) = delete; ~LuaRemover() { - lua_remove(mState, mIndex); + // If we're unwinding the C++ stack due to an exception, don't mess + // with the Lua stack! + if (std::uncaught_exceptions() == 0) + lua_remove(mState, mIndex); } private: -- cgit v1.2.3 From 4ae12a08265998055627a0530f6e7ef8da5ca2ed Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 11:05:24 -0400 Subject: Add LLAgent.teleport() Lua function that wraps existing "LLTeleportHandler" LEAP listener. --- indra/newview/scripts/lua/require/LLAgent.lua | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'indra') diff --git a/indra/newview/scripts/lua/require/LLAgent.lua b/indra/newview/scripts/lua/require/LLAgent.lua index 07ef1e0b0b..9eebede59f 100644 --- a/indra/newview/scripts/lua/require/LLAgent.lua +++ b/indra/newview/scripts/lua/require/LLAgent.lua @@ -71,4 +71,12 @@ function LLAgent.getAnimationInfo(item_id) return leap.request('LLAgent', {op = 'getAnimationInfo', item_id=item_id}).anim_info end +-- Teleport to specified "regionname" at specified region-relative "x", "y", "z". +-- If "regionname" omitted, teleport to GLOBAL coordinates "x", "y", "z". +function LLAgent.teleport(...) + local args = mapargs('regionname,x,y,z', ...) + args.op = 'teleport' + return leap.request('LLTeleportHandler', args) +end + return LLAgent -- cgit v1.2.3 From 499c62637f487b673bac86be566588a8ccde388d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 11:14:40 -0400 Subject: Recursively package all of indra/newview/scripts/lua. Instead of trying to continue mirroring the lua subdirectory structure in viewer_manifest.py, and enumerating the relevant file extensions, just pack up the whole subtree. --- indra/newview/viewer_manifest.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index c403b57813..9f77dba3bc 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -166,12 +166,7 @@ class ViewerManifest(LLManifest): self.path("*/*/*/*.js") self.path("*/*/*.html") - with self.prefix(src_dst="scripts/lua"): - self.path("*.lua") - self.path("*.xml") - for subdir in 'require', 'auto': - with self.prefix(src_dst=subdir): - self.path("*.lua") + self.path('scripts/lua') #build_data.json. Standard with exception handling is fine. If we can't open a new file for writing, we have worse problems #platform is computed above with other arg parsing -- cgit v1.2.3 From a68bb4eb27573a9313169799a188dda3ae765666 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 12:27:08 -0400 Subject: Support "LLTeleportHandler" "teleport" regionname="home". --- indra/newview/llurldispatcher.cpp | 9 ++++++++- indra/newview/scripts/lua/require/LLAgent.lua | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/newview/llurldispatcher.cpp b/indra/newview/llurldispatcher.cpp index 39a9f0f8bc..166542324d 100644 --- a/indra/newview/llurldispatcher.cpp +++ b/indra/newview/llurldispatcher.cpp @@ -289,6 +289,8 @@ public: LLEventAPI::add("teleport", "Teleport to specified [\"regionname\"] at\n" "specified region-relative [\"x\"], [\"y\"], [\"z\"].\n" + "If [\"regionname\"] is \"home\", ignore [\"x\"], [\"y\"], [\"z\"]\n" + "and teleport home.\n" "If [\"regionname\"] omitted, teleport to GLOBAL\n" "coordinates [\"x\"], [\"y\"], [\"z\"].", &LLTeleportHandler::from_event); @@ -328,7 +330,12 @@ public: void from_event(const LLSD& params) const { Response response(LLSD(), params); - if (params.has("regionname")) + if (params["regionname"].asString() == "home") + { + gAgent.teleportHome(); + response["message"] = "Teleporting home"; + } + else if (params.has("regionname")) { // region specified, coordinates (if any) are region-local LLVector3 local_pos( diff --git a/indra/newview/scripts/lua/require/LLAgent.lua b/indra/newview/scripts/lua/require/LLAgent.lua index 9eebede59f..5cee998fcd 100644 --- a/indra/newview/scripts/lua/require/LLAgent.lua +++ b/indra/newview/scripts/lua/require/LLAgent.lua @@ -72,11 +72,12 @@ function LLAgent.getAnimationInfo(item_id) end -- Teleport to specified "regionname" at specified region-relative "x", "y", "z". +-- If "regionname" is "home", ignore "x", "y", "z" and teleport home. -- If "regionname" omitted, teleport to GLOBAL coordinates "x", "y", "z". function LLAgent.teleport(...) local args = mapargs('regionname,x,y,z', ...) args.op = 'teleport' - return leap.request('LLTeleportHandler', args) + return leap.request('LLTeleportHandler', args).message end return LLAgent -- cgit v1.2.3 From 6c6a42fbbc231bad6a7921c37cdcb82d17dc225f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 13:46:30 -0400 Subject: Let test_animation.lua cope with the case of 0 animations. --- indra/newview/scripts/lua/test_animation.lua | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/test_animation.lua b/indra/newview/scripts/lua/test_animation.lua index c16fef4918..37e7254a6c 100644 --- a/indra/newview/scripts/lua/test_animation.lua +++ b/indra/newview/scripts/lua/test_animation.lua @@ -11,18 +11,22 @@ for key in pairs(anims) do table.insert(anim_ids, key) end --- Start playing a random animation -math.randomseed(os.time()) -local random_id = anim_ids[math.random(#anim_ids)] -local anim_info = LLAgent.getAnimationInfo(random_id) +if #anim_ids == 0 then + print("No animations found") +else + -- Start playing a random animation + math.randomseed(os.time()) + local random_id = anim_ids[math.random(#anim_ids)] + local anim_info = LLAgent.getAnimationInfo(random_id) -print("Starting animation locally: " .. anims[random_id].name) -print("Loop: " .. anim_info.is_loop .. " Joints: " .. anim_info.num_joints .. " Duration " .. tonumber(string.format("%.2f", anim_info.duration))) -LLAgent.playAnimation{item_id=random_id} + print("Starting animation locally: " .. anims[random_id].name) + print("Loop: " .. anim_info.is_loop .. " Joints: " .. anim_info.num_joints .. " Duration " .. tonumber(string.format("%.2f", anim_info.duration))) + LLAgent.playAnimation{item_id=random_id} --- Stop animation after 3 sec if it's looped or longer than 3 sec -if anim_info.is_loop == 1 or anim_info.duration > 3 then - LL.sleep(3) - print("Stop animation.") - LLAgent.stopAnimation(random_id) + -- Stop animation after 3 sec if it's looped or longer than 3 sec + if anim_info.is_loop == 1 or anim_info.duration > 3 then + LL.sleep(3) + print("Stop animation.") + LLAgent.stopAnimation(random_id) + end end -- cgit v1.2.3 From 2b851fbd0b4f72f46ec68965b3e06451e20a5b39 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 15:08:54 -0400 Subject: Mediate "LLAppViewer" "userQuit" et al. via "mainloop" WorkQueue. Empirically, this works better than engaging the respective LLAppViewer methods directly. --- indra/newview/llappviewerlistener.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'indra') diff --git a/indra/newview/llappviewerlistener.cpp b/indra/newview/llappviewerlistener.cpp index d02b1b4a79..4690c91b61 100644 --- a/indra/newview/llappviewerlistener.cpp +++ b/indra/newview/llappviewerlistener.cpp @@ -35,6 +35,7 @@ // external library headers // other Linden headers #include "llappviewer.h" +#include "workqueue.h" LLAppViewerListener::LLAppViewerListener(const LLAppViewerGetter& getter): LLEventAPI("LLAppViewer", @@ -56,17 +57,23 @@ LLAppViewerListener::LLAppViewerListener(const LLAppViewerGetter& getter): void LLAppViewerListener::userQuit(const LLSD& event) { LL_INFOS() << "Listener requested user quit" << LL_ENDL; - mAppViewerGetter()->userQuit(); + // Trying to engage this from (e.g.) a Lua-hosting C++ coroutine runs + // afoul of an assert in the logging machinery that LLMutex must be locked + // only from the main coroutine. + LL::WorkQueue::getInstance("mainloop")->post( + [appviewer=mAppViewerGetter()]{ appviewer->userQuit(); }); } void LLAppViewerListener::requestQuit(const LLSD& event) { LL_INFOS() << "Listener requested quit" << LL_ENDL; - mAppViewerGetter()->requestQuit(); + LL::WorkQueue::getInstance("mainloop")->post( + [appviewer=mAppViewerGetter()]{ appviewer->requestQuit(); }); } void LLAppViewerListener::forceQuit(const LLSD& event) { LL_INFOS() << "Listener requested force quit" << LL_ENDL; - mAppViewerGetter()->forceQuit(); + LL::WorkQueue::getInstance("mainloop")->post( + [appviewer=mAppViewerGetter()]{ appviewer->forceQuit(); }); } -- cgit v1.2.3 From 1b7fdac4689e29ec3f64c37f10b843a114d7805d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 15:09:24 -0400 Subject: Add frame_profile.lua to TP to known spot and take frame profile. frame_profile.lua teleports home when done. Further add frame_profile bash script to run the specified viewer, automatically log into said known spot, take frame profile and quit. The frame_profile bash script runs frame_profile_quit.lua. frame_profile_quit.lua is derived from frame_profile.lua, but different: it doesn't teleport either way because it assumes autologin to the target location, and because it logs out instead of returning home. --- indra/newview/scripts/lua/frame_profile.lua | 24 +++++++++++++++++++++++ indra/newview/scripts/lua/frame_profile_quit.lua | 25 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 indra/newview/scripts/lua/frame_profile.lua create mode 100644 indra/newview/scripts/lua/frame_profile_quit.lua (limited to 'indra') diff --git a/indra/newview/scripts/lua/frame_profile.lua b/indra/newview/scripts/lua/frame_profile.lua new file mode 100644 index 0000000000..3c6353ff68 --- /dev/null +++ b/indra/newview/scripts/lua/frame_profile.lua @@ -0,0 +1,24 @@ +-- Trigger Develop -> Render Tests -> Frame Profile + +LLAgent = require 'LLAgent' +startup = require 'startup' +Timer = (require 'timers').Timer +UI = require 'UI' + +startup.wait('STATE_STARTED') + +-- teleport to http://maps.secondlife.com/secondlife/Bug%20Island/220/224/27 +print(LLAgent.teleport{regionname='Bug Island', x=220, y=224, z=27}) +Timer(10, 'wait') +LLAgent.setCamera{camera_pos={220, 224, 26}, camera_locked=true, + focus_pos ={228, 232, 26}, focus_locked=true} +Timer(1, 'wait') +-- This freezes the viewer for perceptible realtime +UI.popup:tip('starting Render Tests -> Frame Profile') +UI.call("Advanced.ClickRenderProfile") +Timer(1, 'wait') +LLAgent.removeCamParams() +LLAgent.setFollowCamActive(false) + +-- Home, James! +print(LLAgent.teleport('home')) diff --git a/indra/newview/scripts/lua/frame_profile_quit.lua b/indra/newview/scripts/lua/frame_profile_quit.lua new file mode 100644 index 0000000000..e3177a3f67 --- /dev/null +++ b/indra/newview/scripts/lua/frame_profile_quit.lua @@ -0,0 +1,25 @@ +-- Trigger Develop -> Render Tests -> Frame Profile and quit + +LLAgent = require 'LLAgent' +logout = require 'logout' +startup = require 'startup' +Timer = (require 'timers').Timer +UI = require 'UI' + +startup.wait('STATE_STARTED') + +-- Assume we logged into http://maps.secondlife.com/secondlife/Bug%20Island/220/224/27 +-- (see frame_profile bash script) +Timer(10, 'wait') +LLAgent.setCamera{camera_pos={220, 224, 26}, camera_locked=true, + focus_pos ={228, 232, 26}, focus_locked=true} +Timer(1, 'wait') +-- This freezes the viewer for perceptible realtime +UI.popup:tip('starting Render Tests -> Frame Profile') +UI.call("Advanced.ClickRenderProfile") +Timer(1, 'wait') +LLAgent.removeCamParams() +LLAgent.setFollowCamActive(false) + +-- done +logout() -- cgit v1.2.3 From ae2f4f6f6a05e1d6f327887204add2c984999db2 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Fri, 13 Sep 2024 20:40:51 +0200 Subject: Restore LUA debug consoles in viewer menu and tie visibility to feature flag --- indra/newview/skins/default/xui/en/menu_viewer.xml | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'indra') diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 324e868bd5..f4864dabef 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -2583,6 +2583,37 @@ function="World.EnvPreset" parameter="scene monitor" /> + + + + + + + + + + + + + Date: Fri, 13 Sep 2024 16:01:05 -0400 Subject: Add context info to Develop->Render Tests->Frame Profile stats file. Specifically, add the viewer version, the machine ID, the grid, the region name and ID, the parcel name and ID and the timestamp. This is both richer and less fragile than trying to extract that information from the generated filename: e.g. we now have region and parcel names. Instead of making `LLGLSLShader::finishProfile()` mess with file I/O, pass it a reference to a `boost::json::value` to be filled in with statistics, if it's a `boost::json::object`. Otherwise it's `boost::json::null`, meaning no report. Make llviewerdisplay.cpp's `display()` function instantiate a `boost::json::value` to pass to `finishProfile()`. That lets llviewerdisplay.cpp also set the `"context"` entry, with a new `getProfileStatsContext()` function quite similar to `getProfileStatsFilename()`. --- indra/llrender/llglslshader.cpp | 21 ++++--------------- indra/llrender/llglslshader.h | 10 ++++----- indra/newview/llviewerdisplay.cpp | 44 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 52 insertions(+), 23 deletions(-) (limited to 'indra') diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index 56f1533708..6ba5463acd 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -41,8 +41,6 @@ #include "OpenGL/OpenGL.h" #endif -#include - // Print-print list of shader included source files that are linked together via glAttachShader() // i.e. On macOS / OSX the AMD GLSL linker will display an error if a varying is left in an undefined state. #define DEBUG_SHADER_INCLUDES 0 @@ -65,7 +63,7 @@ U64 LLGLSLShader::sTotalTimeElapsed = 0; U32 LLGLSLShader::sTotalTrianglesDrawn = 0; U64 LLGLSLShader::sTotalSamplesDrawn = 0; U32 LLGLSLShader::sTotalBinds = 0; -std::string LLGLSLShader::sDefaultReportName; +boost::json::value LLGLSLShader::sDefaultStats; //UI shader -- declared here so llui_libtest will link properly LLGLSLShader gUIProgram; @@ -120,16 +118,16 @@ struct LLGLSLShaderCompareTimeElapsed }; //static -void LLGLSLShader::finishProfile(const std::string& report_name) +void LLGLSLShader::finishProfile(boost::json::value& statsv) { sProfileEnabled = false; - if (! report_name.empty()) + if (! statsv.is_null()) { std::vector sorted(sInstances.begin(), sInstances.end()); std::sort(sorted.begin(), sorted.end(), LLGLSLShaderCompareTimeElapsed()); - boost::json::object stats; + auto& stats = statsv.as_object(); auto shadersit = stats.emplace("shaders", boost::json::array_kind).first; auto& shaders = shadersit->value().as_array(); bool unbound = false; @@ -174,17 +172,6 @@ void LLGLSLShader::finishProfile(const std::string& report_name) } } } - - std::ofstream outf(report_name); - if (! outf) - { - LL_WARNS() << "Couldn't write to " << std::quoted(report_name) << LL_ENDL; - } - else - { - outf << stats; - LL_INFOS() << "(also dumped to " << std::quoted(report_name) << ")" << LL_ENDL; - } } } diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h index a9b9bfafa8..2d669c70a9 100644 --- a/indra/llrender/llglslshader.h +++ b/indra/llrender/llglslshader.h @@ -170,7 +170,7 @@ public: static U32 sMaxGLTFNodes; static void initProfile(); - static void finishProfile(const std::string& report_name=sDefaultReportName); + static void finishProfile(boost::json::value& stats=sDefaultStats); static void startProfile(); static void stopProfile(); @@ -365,10 +365,10 @@ public: private: void unloadInternal(); // This must be static because finishProfile() is called at least once - // within a __try block. If we default its report_name parameter to a - // temporary std::string, that temporary must be destroyed when the stack - // is unwound, which __try forbids. - static std::string sDefaultReportName; + // within a __try block. If we default its stats parameter to a temporary + // json::value, that temporary must be destroyed when the stack is + // unwound, which __try forbids. + static boost::json::value sDefaultStats; }; //UI shader (declared here so llui_libtest will link properly) diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index fdfe477a6c..aad11d9372 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -138,6 +138,7 @@ void render_ui_3d(); void render_ui_2d(); void render_disconnected_background(); +void getProfileStatsContext(boost::json::object& stats); std::string getProfileStatsFilename(); void display_startup() @@ -1040,8 +1041,49 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) if (gShaderProfileFrame) { gShaderProfileFrame = false; - LLGLSLShader::finishProfile(getProfileStatsFilename()); + boost::json::value stats{ boost::json::object_kind }; + getProfileStatsContext(stats.as_object()); + LLGLSLShader::finishProfile(stats); + + auto report_name = getProfileStatsFilename(); + std::ofstream outf(report_name); + if (! outf) + { + LL_WARNS() << "Couldn't write to " << std::quoted(report_name) << LL_ENDL; + } + else + { + outf << stats; + LL_INFOS() << "(also dumped to " << std::quoted(report_name) << ")" << LL_ENDL; + } + } +} + +void getProfileStatsContext(boost::json::object& stats) +{ + auto contextit = stats.emplace("context", boost::json::object_kind).first; + auto& context = contextit->value().as_object(); + + auto& versionInfo = LLVersionInfo::instance(); + context.emplace("channel", versionInfo.getChannel()); + context.emplace("version", versionInfo.getVersion()); + unsigned char unique_id[MAC_ADDRESS_BYTES]{}; + LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); + context.emplace("machine", stringize(LL::hexdump(unique_id, sizeof(unique_id)))); + context.emplace("grid", LLGridManager::instance().getGrid()); + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + context.emplace("region", region->getName()); + context.emplace("regionid", stringize(region->getRegionID())); + } + LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); + if (parcel) + { + context.emplace("parcel", parcel->getName()); + context.emplace("parcelid", parcel->getLocalID()); } + context.emplace("time", LLDate::now().toHTTPDateString("%Y-%m-%dT%H:%M:%S")); } std::string getProfileStatsFilename() -- cgit v1.2.3 From 328bdeb3cc26648060a3d7331ccdb80539953f33 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 13 Sep 2024 16:52:17 -0400 Subject: Remove autorun 'menus.lua' since menu_viewer.xml handles visibility --- indra/newview/scripts/lua/auto/menus.lua | 51 -------------------------------- 1 file changed, 51 deletions(-) delete mode 100644 indra/newview/scripts/lua/auto/menus.lua (limited to 'indra') diff --git a/indra/newview/scripts/lua/auto/menus.lua b/indra/newview/scripts/lua/auto/menus.lua deleted file mode 100644 index b2f54d83df..0000000000 --- a/indra/newview/scripts/lua/auto/menus.lua +++ /dev/null @@ -1,51 +0,0 @@ --- Inject Lua-related menus into the top menu structure. Run this as a Lua --- script so that turning off the Lua feature also disables these menus. - --- Under Develop -> Consoles, want to present the equivalent of: --- --- --- --- --- --- --- --- --- - -local startup = require 'startup' -local UI = require 'UI' - --- Don't mess with the viewer's menu structure until we've logged in. -startup.wait('STATE_STARTED') - --- Add LUA Debug Console to Develop->Consoles -local pos = 9 -UI.addMenuSeparator{ - parent_menu='Consoles', pos=pos, -} -pos += 1 -UI.addMenuItem{ - parent_menu='Consoles', pos=pos, - name='lua_debug', label='LUA Debug Console', - func='Floater.ToggleOrBringToFront', param='lua_debug', -} -pos += 1 - --- Add LUA Scripts Info to Develop->Consoles -UI.addMenuItem{ - parent_menu='Consoles', pos=pos, - name='lua_scripts', label='LUA Scripts Info', - func='Floater.ToggleOrBringToFront', param='lua_scripts', -} -- cgit v1.2.3 From 1154e02fdded191147284997707a3b18ee3b43fd Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 16 Sep 2024 13:53:11 -0400 Subject: WIP: edits in support of Lua script args --- indra/llcommon/lua_function.cpp | 39 +++++++++++++++++--- indra/llcommon/lua_function.h | 17 +++++++-- indra/newview/llluamanager.cpp | 79 ++++++++++++++++++++++++++++++----------- 3 files changed, 106 insertions(+), 29 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 2557fd0cc9..014ff3c4c4 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -60,7 +60,8 @@ namespace namespace lluau { -int dostring(lua_State* L, const std::string& desc, const std::string& text) +int dostring(lua_State* L, const std::string& desc, const std::string& text, + const std::vector& args) { auto r = loadstring(L, desc, text); if (r != LUA_OK) @@ -80,12 +81,22 @@ int dostring(lua_State* L, const std::string& desc, const std::string& text) // stack: compiled chunk, debug.traceback() lua_insert(L, -2); // stack: debug.traceback(), compiled chunk - LuaRemover cleanup(L, -2); + // capture absolute index of debug.traceback() + int traceback = lua_absindex(L, -2); + // remove it from stack on exit + LuaRemover cleanup(L, traceback); + + // push any args passed -- all strings -- script must handle any desired + // conversions + for (const auto& arg : args) + { + lua_pushstdstring(L, arg); + } // It's important to pass LUA_MULTRET as the expected number of return // values: if we pass any fixed number, we discard any returned values // beyond that number. - return lua_pcall(L, 0, LUA_MULTRET, -2); + return lua_pcall(L, int(args.size()), LUA_MULTRET, traceback); } int loadstring(lua_State *L, const std::string &desc, const std::string &text) @@ -804,7 +815,13 @@ bool LuaState::checkLua(const std::string& desc, int r) return true; } -std::pair LuaState::expr(const std::string& desc, const std::string& text) +std::pair LuaState::expr(const std::string& desc, const ScriptCommand& command) +{ + return expr(desc, , command.args); +} + +std::pair LuaState::expr(const std::string& desc, const std::string& text, + const std::vector& args) { /*---------------------------- feature flag ----------------------------*/ if (! mFeature) @@ -827,7 +844,7 @@ std::pair LuaState::expr(const std::string& desc, const std::string& }; LL_INFOS("Lua") << desc << " run" << LL_ENDL; - if (! checkLua(desc, lluau::dostring(mState, desc, text))) + if (! checkLua(desc, lluau::dostring(mState, desc, text, args))) { LL_WARNS("Lua") << desc << " error: " << mError << LL_ENDL; return { -1, mError }; @@ -1049,6 +1066,18 @@ std::pair LuaFunction::getState() return { registry, lookup }; } +/***************************************************************************** +* LuaCommand +*****************************************************************************/ +LuaCommand::LuaCommand(const std::string& command) +{ +} + +bool LuaCommand::found() const +{ + return ; +} + /***************************************************************************** * source_path() *****************************************************************************/ diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 10c201c234..a5022db225 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -19,6 +19,7 @@ #include "fsyspath.h" #include "llerror.h" #include "llsd.h" +#include "scriptcommand.h" #include "stringize.h" #include // std::uncaught_exceptions() #include // std::shared_ptr @@ -26,6 +27,7 @@ #include #include #include // std::pair +#include class LuaListener; @@ -55,8 +57,10 @@ namespace lluau // luau removed lua_dostring(), but since we perform the equivalent luau // sequence in multiple places, encapsulate it. desc and text are strings // rather than string_views because dostring() needs pointers to nul- - // terminated char arrays. - int dostring(lua_State* L, const std::string& desc, const std::string& text); + // terminated char arrays. Any args are pushed to the Lua stack before + // calling the Lua chunk in text. + int dostring(lua_State* L, const std::string& desc, const std::string& text, + const std::vector& args={}); int loadstring(lua_State* L, const std::string& desc, const std::string& text); fsyspath source_path(lua_State* L); @@ -92,13 +96,20 @@ public: // expr() is for when we want to capture any results left on the stack // by a Lua expression, possibly including multiple return values. + // Pass: + // desc = description used for logging et al. + // text = Lua chunk to execute, e.g. contents of a script file + // args = arguments, if any, to pass to script file + // Returns: // 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 expr(const std::string& desc, const std::string& text); + std::pair expr(const std::string& desc, const std::string& text, + const std::vector& args={}); + std::pair expr(const std::string& desc, const ScriptCommand& command); operator lua_State*() const { return mState; } diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 0bd21516bc..c4ce07e492 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -192,35 +192,72 @@ void LLLUAmanager::runScriptFile(const std::string &filename, bool autorun, LLCoros::instance().launch(filename, [filename, autorun, result_cb, finished_cb]() { ScriptObserver observer(LLCoros::getName(), filename); - llifstream in_file; - in_file.open(filename.c_str()); + // Use LLStringUtil::getTokens() to parse the script command line + auto tokens = LLStringUtil::getTokens(filename, + " \t\r\n", // drop_delims + "", // no keep_delims + "\"'", // either kind of quotes + "\\"); // backslash escape + +#error Under construction + // TODO: + // - Move this either/or file existence logic to LuaCommand() + // - Accept LuaCommand in all LLLUAmanager::mumbleScriptFile() methods + // - Update all existing mumbleScriptFile() callers + llifstream in_file; + in_file.open(tokens[0]); if (in_file.is_open()) { - if (autorun) - { - sAutorunScriptCount++; - } - sScriptCount++; - - // A script_finished_fn is used to initialize the LuaState. - // It will be called when the LuaState is destroyed. - LuaState L(finished_cb); - std::string text{std::istreambuf_iterator(in_file), {}}; - auto [count, result] = L.expr(filename, text); - if (result_cb) - { - result_cb(count, result); - } + // The first token is in fact the script filename. Now that the + // script file is open, we've consumed that token. The rest are + // command-line arguments. + tokens.erase(tokens.begin()); } else { - auto msg{ stringize("unable to open script file '", filename, "'") }; - LL_WARNS("Lua") << msg << LL_ENDL; - if (result_cb) + // Parsing the command line produced a script file path we can't + // open. Maybe that's because there are spaces in the original + // pathname that were neither quoted nor escaped? See if we can + // open the whole original command line string. + in_file.open(filename); + if (! in_file.is_open()) { - result_cb(-1, msg); + std::ostringstream msgstream; + msgstream << "Can't open script file " << std::quoted(tokens[0]); + if (filename != tokens[0]) + { + msgstream << " or " << std::quoted(filename); + } + auto msg = msgstream.str(); + LL_WARNS("Lua") << msg << LL_ENDL; + if (result_cb) + { + result_cb(-1, msg); + } + return; } + // Here we do have in_file open, using the whole input command + // line as its pathname. Discard any parts of it we mistook for + // command-line arguments. + tokens.clear(); + } + + // Here in_file is open. + if (autorun) + { + sAutorunScriptCount++; + } + sScriptCount++; + + // A script_finished_fn is used to initialize the LuaState. + // It will be called when the LuaState is destroyed. + LuaState L(finished_cb); + std::string text{std::istreambuf_iterator(in_file), {}}; + auto [count, result] = L.expr(filename, text, tokens); + if (result_cb) + { + result_cb(count, result); } }); } -- cgit v1.2.3 From f4b65638879c10c832b3bb8448f82001106ffd11 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 16 Sep 2024 17:25:48 -0400 Subject: Add LLFloaterAbout info (esp. GPU info) to Frame Profile stats dump With the About info added, `getProfileStatsContext()` need not redundantly add `"channel"`, `"version"` or `"region"`. Slightly improve the efficiency of `LlsdToJson()` and `LlsdFromJson()` by preallocating the known size of the source array or map. (Unfortunately the C++ `LLSD` class offers us no way to preallocate a map.) In `LLAppViewer::getViewerInfo()`, avoid immediate successive calls to `gAgent.getRegion()`. --- indra/llcommon/llsdjson.cpp | 15 +++++++++++++-- indra/newview/llappviewer.cpp | 4 ++-- indra/newview/llviewerdisplay.cpp | 10 +++++----- 3 files changed, 20 insertions(+), 9 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llsdjson.cpp b/indra/llcommon/llsdjson.cpp index 5d38e55686..1df2a8f9eb 100644 --- a/indra/llcommon/llsdjson.cpp +++ b/indra/llcommon/llsdjson.cpp @@ -61,12 +61,20 @@ LLSD LlsdFromJson(const boost::json::value& val) result = LLSD(val.as_bool()); break; case boost::json::kind::array: + { result = LLSD::emptyArray(); - for (const auto &element : val.as_array()) + auto& array = val.as_array(); + // allocate elements 0 .. (size() - 1) to avoid incremental allocation + if (! array.empty()) + { + result[array.size() - 1] = LLSD(); + } + for (const auto &element : array) { result.append(LlsdFromJson(element)); } break; + } case boost::json::kind::object: result = LLSD::emptyMap(); for (const auto& element : val.as_object()) @@ -106,6 +114,7 @@ boost::json::value LlsdToJson(const LLSD &val) case LLSD::TypeMap: { boost::json::object& obj = result.emplace_object(); + obj.reserve(val.size()); for (const auto& llsd_dat : llsd::inMap(val)) { obj[llsd_dat.first] = LlsdToJson(llsd_dat.second); @@ -115,6 +124,7 @@ boost::json::value LlsdToJson(const LLSD &val) case LLSD::TypeArray: { boost::json::array& json_array = result.emplace_array(); + json_array.reserve(val.size()); for (const auto& llsd_dat : llsd::inArray(val)) { json_array.push_back(LlsdToJson(llsd_dat)); @@ -123,7 +133,8 @@ boost::json::value LlsdToJson(const LLSD &val) } case LLSD::TypeBinary: default: - LL_ERRS("LlsdToJson") << "Unsupported conversion to JSON from LLSD type (" << val.type() << ")." << LL_ENDL; + LL_ERRS("LlsdToJson") << "Unsupported conversion to JSON from LLSD type (" + << val.type() << ")." << LL_ENDL; break; } diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 789aaab70d..77797ad5f0 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -3351,10 +3351,10 @@ LLSD LLAppViewer::getViewerInfo() const LLVector3d pos = gAgent.getPositionGlobal(); info["POSITION"] = ll_sd_from_vector3d(pos); info["POSITION_LOCAL"] = ll_sd_from_vector3(gAgent.getPosAgentFromGlobal(pos)); - info["REGION"] = gAgent.getRegion()->getName(); + info["REGION"] = region->getName(); boost::regex regex("\\.(secondlife|lindenlab)\\..*"); - info["HOSTNAME"] = boost::regex_replace(gAgent.getRegion()->getSimHostName(), regex, ""); + info["HOSTNAME"] = boost::regex_replace(region->getSimHostName(), regex, ""); info["SERVER_VERSION"] = gLastVersionChannel; LLSLURL slurl; LLAgentUI::buildSLURL(slurl); diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index aad11d9372..f722d0bd1d 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -58,6 +58,7 @@ #include "llpostprocess.h" #include "llrender.h" #include "llscenemonitor.h" +#include "llsdjson.h" #include "llselectmgr.h" #include "llsky.h" #include "llspatialpartition.h" @@ -1061,12 +1062,12 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) void getProfileStatsContext(boost::json::object& stats) { - auto contextit = stats.emplace("context", boost::json::object_kind).first; + // populate the context with info from LLFloaterAbout + auto contextit = stats.emplace("context", + LlsdToJson(LLAppViewer::instance()->getViewerInfo())).first; auto& context = contextit->value().as_object(); - auto& versionInfo = LLVersionInfo::instance(); - context.emplace("channel", versionInfo.getChannel()); - context.emplace("version", versionInfo.getVersion()); + // then add a few more things unsigned char unique_id[MAC_ADDRESS_BYTES]{}; LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); context.emplace("machine", stringize(LL::hexdump(unique_id, sizeof(unique_id)))); @@ -1074,7 +1075,6 @@ void getProfileStatsContext(boost::json::object& stats) LLViewerRegion* region = gAgent.getRegion(); if (region) { - context.emplace("region", region->getName()); context.emplace("regionid", stringize(region->getRegionID())); } LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); -- cgit v1.2.3 From 50513bab2d6b1823f983c145553b8a6af44c2f28 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 19 Sep 2024 15:00:37 -0400 Subject: Make login.lua enhance plain grid='agni' to required form. --- indra/newview/scripts/lua/require/login.lua | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/require/login.lua b/indra/newview/scripts/lua/require/login.lua index 919434f3a5..37c9093a21 100644 --- a/indra/newview/scripts/lua/require/login.lua +++ b/indra/newview/scripts/lua/require/login.lua @@ -18,16 +18,25 @@ local function ensure_login_state(op) end end +local function fullgrid(grid) + if string.find(grid, '.', 1, true) then + return grid + else + return `util.{grid}.secondlife.com` + end +end + function login.login(...) ensure_login_state('login') local args = mapargs('username,grid,slurl', ...) args.op = 'login' + args.grid = fullgrid(args.grid) return leap.request('LLPanelLogin', args) end function login.savedLogins(grid) ensure_login_state('savedLogins') - return leap.request('LLPanelLogin', {op='savedLogins', grid=grid})['logins'] + return leap.request('LLPanelLogin', {op='savedLogins', grid=fullgrid(grid)})['logins'] end return login -- cgit v1.2.3 From 6d29beb91b019e1995cdb7c4aaf7a043de4bf053 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 20 Sep 2024 15:13:43 -0400 Subject: Add ability to pass command-line arguments to a Lua script. Introduce `ScriptCommand` class that parses a command line into a script name and optional args, using bash-like quoting and escaping. `ScriptCommand` searches for a file with that script name on a passed list of directories; the directories may be specified relative to a particular base directory. `ScriptCommand` supports the special case of a script name containing unescaped spaces. It guarantees that either the returned script file exists, or its `error()` string is non-empty. Replace `LLLeap::create()` logic, from which `ScriptCommand` was partly derived, with a `ScriptCommand` instance. Make `LLLUAmanager::runScriptFile()` use a `ScriptCommand` instance to parse the passed command line. Subsume `LLAppViewer::init()` script-path-searching logic for `--luafile` into `ScriptCommand`. In fact that lambda now simply calls `LLLUAmanager::runScriptFile()`. Make `lluau::dostring()` accept an optional vector of script argument strings. Following PUC-Rio Lua convention, pass these arguments into a Lua script as the predefined global `arg`, and also as the script's `...` argument. `LuaState::expr()` also accepts and passes through script argument strings. Change the log tag for the Lua script interruption message: if we want it, we can still enable it, but we don't necessarily want it along with all other "Lua" DEBUG messages. Remove `LuaState::script_finished_fn`, which isn't used any more. Also remove the corresponding `LLLUAmanager::script_finished_fn`. This allows us to simplify `~LuaState()` slightly, as well as the parameter signatures for `LLLUAmanager::runScriptFile()` and `runScriptLine()`. --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/llleap.cpp | 45 +++++---- indra/llcommon/lua_function.cpp | 199 ++++++++++++++++++++++----------------- indra/llcommon/lua_function.h | 6 +- indra/llcommon/scriptcommand.cpp | 117 +++++++++++++++++++++++ indra/llcommon/scriptcommand.h | 64 +++++++++++++ indra/newview/llappviewer.cpp | 26 ++--- indra/newview/llluamanager.cpp | 83 +++++----------- indra/newview/llluamanager.h | 22 +++-- 9 files changed, 368 insertions(+), 196 deletions(-) create mode 100644 indra/llcommon/scriptcommand.cpp create mode 100644 indra/llcommon/scriptcommand.h (limited to 'indra') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index f47136f781..9b99d86434 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -108,6 +108,7 @@ set(llcommon_SOURCE_FILES lua_function.cpp lualistener.cpp resultset.cpp + scriptcommand.cpp threadpool.cpp throttle.cpp u64.cpp @@ -257,6 +258,7 @@ set(llcommon_HEADER_FILES lua_function.h lualistener.h resultset.h + scriptcommand.h stdtypes.h stringize.h tempset.h diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index 306d4e48d0..ef43af6ceb 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -21,17 +21,18 @@ // external library headers // other Linden headers #include "llerror.h" -#include "llstring.h" -#include "llprocess.h" +#include "llerrorcontrol.h" #include "llevents.h" -#include "stringize.h" -#include "llsdutil.h" +#include "llexception.h" +#include "llleaplistener.h" +#include "llprocess.h" #include "llsdserialize.h" -#include "llerrorcontrol.h" +#include "llsdutil.h" +#include "llstring.h" #include "lltimer.h" #include "lluuid.h" -#include "llleaplistener.h" -#include "llexception.h" +#include "scriptcommand.h" +#include "stringize.h" #if LL_MSVC #pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally @@ -107,7 +108,7 @@ public: // If that didn't work, no point in keeping this LLLeap object. if (! mChild) { - LLTHROW(Error(STRINGIZE("failed to run " << mDesc))); + LLTHROW(Error(stringize("failed to run ", mDesc))); } // Okay, launch apparently worked. Change our mDonePump listener. @@ -484,12 +485,24 @@ LLLeap* LLLeap::create(const std::string& desc, const std::vector& LLLeap* LLLeap::create(const std::string& desc, const std::string& plugin, bool exc) { - // Use LLStringUtil::getTokens() to parse the command line - return create(desc, - LLStringUtil::getTokens(plugin, - " \t\r\n", // drop_delims - "", // no keep_delims - "\"'", // either kind of quotes - "\\"), // backslash escape - exc); + // Use ScriptCommand to parse the command line + ScriptCommand command(plugin); + auto error = command.error(); + if (! error.empty()) + { + if (exc) + { + LLTHROW(Error(error)); + } + return nullptr; + } + + LLProcess::Params params; + params.desc = desc; + params.executable = command.script; + for (const auto& arg : command.args) + { + params.args.add(arg); + } + return create(params, exc); } diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 014ff3c4c4..a9f88f3170 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -63,6 +63,8 @@ namespace lluau int dostring(lua_State* L, const std::string& desc, const std::string& text, const std::vector& args) { + // debug.traceback() + compiled chunk + args table + args... + slop + lluau_checkstack(L, 1 + 1 + 1 + int(args.size()) + 2); auto r = loadstring(L, desc, text); if (r != LUA_OK) return r; @@ -86,12 +88,56 @@ int dostring(lua_State* L, const std::string& desc, const std::string& text, // remove it from stack on exit LuaRemover cleanup(L, traceback); - // push any args passed -- all strings -- script must handle any desired - // conversions + // Originally we just pushed 'args' to the Lua stack before entering the + // chunk. But that's awkward for the chunk: it must reference those + // arguments as '...', using any of a number of tactics to move them to + // named variables. This doesn't work as a Lua chunk expecting arguments: + // function(a, b, c) + // -- ... + // end + // because that code only *defines* a function: the function's body isn't + // entered by executing the chunk. + // + // Per https://www.lua.org/manual/5.1/manual.html#6 Lua Stand-alone, we + // now also create a global table called 'arg' whose [0] is the script + // name, ['n'] is the number of additional arguments and [1] through + // [['n']] are the additional arguments. We diverge from that spec in not + // creating any negative indices. + // + // Since the spec notes that the chunk can also reference args using + // '...', we also leave them on the stack. + + // stack: debug.traceback(), compiled chunk + // create arg table pre-sized to hold the args array, plus [0] and ['n'] + lua_createtable(L, narrow(args.size()), 2); + // stack: debug.traceback(), compiled chunk, arg table + int argi = lua_absindex(L, -1); + lua_Integer i = 0; + // store desc (e.g. script name) as arg[0] + lua_pushinteger(L, i); + lua_pushstdstring(L, desc); + lua_rawset(L, argi); // rawset() pops key and value + // store args.size() as arg.n + lua_pushinteger(L, narrow(args.size())); + lua_setfield(L, argi, "n"); // setfield() pops value for (const auto& arg : args) { + // push each arg in order lua_pushstdstring(L, arg); + // push index + lua_pushinteger(L, ++i); + // duplicate arg[i] to store in arg table + lua_pushvalue(L, -2); + // stack: ..., arg[i], i, arg[i] + lua_rawset(L, argi); + // leave ..., arg[i] on stack } + // stack: debug.traceback(), compiled chunk, arg, arg[1], arg[2], ... + // duplicate the arg table to store it + lua_pushvalue(L, argi); + lua_setglobal(L, "arg"); + lua_remove(L, argi); + // stack: debug.traceback(), compiled chunk, arg[1], arg[2], ... // It's important to pass LUA_MULTRET as the expected number of return // values: if we pass any fixed number, we discard any returned values @@ -101,6 +147,7 @@ int dostring(lua_State* L, const std::string& desc, const std::string& text, int loadstring(lua_State *L, const std::string &desc, const std::string &text) { + lluau_checkstack(L, 1); size_t bytecodeSize = 0; // The char* returned by luau_compile() must be freed by calling free(). // Use unique_ptr so the memory will be freed even if luau_load() throws. @@ -540,8 +587,7 @@ int lua_metaipair(lua_State* L); } // anonymous namespace -LuaState::LuaState(script_finished_fn cb): - mCallback(cb) +LuaState::LuaState() { /*---------------------------- feature flag ----------------------------*/ try @@ -726,80 +772,74 @@ LuaState::~LuaState() return; /*---------------------------- feature flag ----------------------------*/ - if (mFeature) + if (! mFeature) + return; /*---------------------------- feature flag ----------------------------*/ + + // We're just about to destroy this lua_State mState. Did this Lua chunk + // register any atexit() functions? + lluau_checkstack(mState, 3); + // look up Registry.atexit + lua_getfield(mState, LUA_REGISTRYINDEX, "atexit"); + // stack contains Registry.atexit + if (lua_istable(mState, -1)) { - // We're just about to destroy this lua_State mState. Did this Lua chunk - // register any atexit() functions? - lluau_checkstack(mState, 3); - // look up Registry.atexit - lua_getfield(mState, LUA_REGISTRYINDEX, "atexit"); - // stack contains Registry.atexit - if (lua_istable(mState, -1)) + // We happen to know that Registry.atexit is built by appending array + // entries using table.insert(). That's important because it means + // there are no holes, and therefore lua_objlen() should be correct. + // That's important because we walk the atexit table backwards, to + // destroy last the things we created (passed to LL.atexit()) first. + int len(lua_objlen(mState, -1)); + LL_DEBUGS("Lua") << LLCoros::getName() << ": Registry.atexit is a table with " + << len << " entries" << LL_ENDL; + + // Push debug.traceback() onto the stack as lua_pcall()'s error + // handler function. On error, lua_pcall() calls the specified error + // handler function with the original error message; the message + // returned by the error handler is then returned by lua_pcall(). + // Luau's debug.traceback() is called with a message to prepend to the + // returned traceback string. Almost as if they'd been designed to + // work together... + lua_getglobal(mState, "debug"); + lua_getfield(mState, -1, "traceback"); + // ditch "debug" + lua_remove(mState, -2); + // stack now contains atexit, debug.traceback() + + for (int i(len); i >= 1; --i) { - // We happen to know that Registry.atexit is built by appending array - // entries using table.insert(). That's important because it means - // there are no holes, and therefore lua_objlen() should be correct. - // That's important because we walk the atexit table backwards, to - // destroy last the things we created (passed to LL.atexit()) first. - int len(lua_objlen(mState, -1)); - LL_DEBUGS("Lua") << LLCoros::getName() << ": Registry.atexit is a table with " - << len << " entries" << LL_ENDL; - - // Push debug.traceback() onto the stack as lua_pcall()'s error - // handler function. On error, lua_pcall() calls the specified error - // handler function with the original error message; the message - // returned by the error handler is then returned by lua_pcall(). - // Luau's debug.traceback() is called with a message to prepend to the - // returned traceback string. Almost as if they'd been designed to - // work together... - lua_getglobal(mState, "debug"); - lua_getfield(mState, -1, "traceback"); - // ditch "debug" - lua_remove(mState, -2); - // stack now contains atexit, debug.traceback() - - for (int i(len); i >= 1; --i) + lua_pushinteger(mState, i); + // stack contains Registry.atexit, debug.traceback(), i + lua_gettable(mState, -3); + // stack contains Registry.atexit, debug.traceback(), atexit[i] + // Call atexit[i](), no args, no return values. + // Use lua_pcall() because errors in any one atexit() function + // shouldn't cancel the rest of them. Pass debug.traceback() as + // the error handler function. + LL_DEBUGS("Lua") << LLCoros::getName() + << ": calling atexit(" << i << ")" << LL_ENDL; + if (lua_pcall(mState, 0, 0, -2) != LUA_OK) { - lua_pushinteger(mState, i); - // stack contains Registry.atexit, debug.traceback(), i - lua_gettable(mState, -3); - // stack contains Registry.atexit, debug.traceback(), atexit[i] - // Call atexit[i](), no args, no return values. - // Use lua_pcall() because errors in any one atexit() function - // shouldn't cancel the rest of them. Pass debug.traceback() as - // the error handler function. - LL_DEBUGS("Lua") << LLCoros::getName() - << ": calling atexit(" << i << ")" << LL_ENDL; - if (lua_pcall(mState, 0, 0, -2) != LUA_OK) - { - auto error{ lua_tostdstring(mState, -1) }; - LL_WARNS("Lua") << LLCoros::getName() - << ": atexit(" << i << ") error: " << error << LL_ENDL; - // pop error message - lua_pop(mState, 1); - } - LL_DEBUGS("Lua") << LLCoros::getName() << ": atexit(" << i << ") done" << LL_ENDL; - // lua_pcall() has already popped atexit[i]: - // stack contains atexit, debug.traceback() + auto error{ lua_tostdstring(mState, -1) }; + LL_WARNS("Lua") << LLCoros::getName() + << ": atexit(" << i << ") error: " << error << LL_ENDL; + // pop error message + lua_pop(mState, 1); } - // pop debug.traceback() - lua_pop(mState, 1); + LL_DEBUGS("Lua") << LLCoros::getName() << ": atexit(" << i << ") done" << LL_ENDL; + // lua_pcall() has already popped atexit[i]: + // stack contains atexit, debug.traceback() } - // pop Registry.atexit (either table or nil) + // pop debug.traceback() lua_pop(mState, 1); - - // with the demise of this LuaState, remove sLuaStateMap entry - sLuaStateMap.erase(mState); - - lua_close(mState); } + // pop Registry.atexit (either table or nil) + lua_pop(mState, 1); - if (mCallback) - { - // mError potentially set by previous checkLua() call(s) - mCallback(mError); - } + // with the demise of this LuaState, remove sLuaStateMap entry + sLuaStateMap.erase(mState); + + lua_close(mState); } bool LuaState::checkLua(const std::string& desc, int r) @@ -815,11 +855,6 @@ bool LuaState::checkLua(const std::string& desc, int r) return true; } -std::pair LuaState::expr(const std::string& desc, const ScriptCommand& command) -{ - return expr(desc, , command.args); -} - std::pair LuaState::expr(const std::string& desc, const std::string& text, const std::vector& args) { @@ -969,8 +1004,8 @@ void LuaState::check_interrupts_counter() } else if (mInterrupts % INTERRUPTS_SUSPEND_LIMIT == 0) { - LL_DEBUGS("Lua") << LLCoros::getName() << " suspending at " << mInterrupts - << " interrupts" << LL_ENDL; + LL_DEBUGS("Lua.suspend") << LLCoros::getName() << " suspending at " + << mInterrupts << " interrupts" << LL_ENDL; llcoro::suspend(); } } @@ -1066,18 +1101,6 @@ std::pair LuaFunction::getState() return { registry, lookup }; } -/***************************************************************************** -* LuaCommand -*****************************************************************************/ -LuaCommand::LuaCommand(const std::string& command) -{ -} - -bool LuaCommand::found() const -{ - return ; -} - /***************************************************************************** * source_path() *****************************************************************************/ diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index a5022db225..ae6e0bf7ba 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -83,9 +83,7 @@ void lua_pushllsd(lua_State* L, const LLSD& data); class LuaState { public: - typedef std::function script_finished_fn; - - LuaState(script_finished_fn cb={}); + LuaState(); LuaState(const LuaState&) = delete; LuaState& operator=(const LuaState&) = delete; @@ -109,7 +107,6 @@ public: // multiple results, represented as the entries of the array. std::pair expr(const std::string& desc, const std::string& text, const std::vector& args={}); - std::pair expr(const std::string& desc, const ScriptCommand& command); operator lua_State*() const { return mState; } @@ -129,7 +126,6 @@ private: /*---------------------------- feature flag ----------------------------*/ bool mFeature{ false }; /*---------------------------- feature flag ----------------------------*/ - script_finished_fn mCallback; lua_State* mState{ nullptr }; std::string mError; S32 mInterrupts{ 0 }; diff --git a/indra/llcommon/scriptcommand.cpp b/indra/llcommon/scriptcommand.cpp new file mode 100644 index 0000000000..79afbc2063 --- /dev/null +++ b/indra/llcommon/scriptcommand.cpp @@ -0,0 +1,117 @@ +/** + * @file scriptcommand.cpp + * @author Nat Goodspeed + * @date 2024-09-16 + * @brief Implementation for scriptcommand. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "scriptcommand.h" +// STL headers +// std headers +#include +// external library headers +// other Linden headers +#include "fsyspath.h" +#include "llerror.h" +#include "llsdutil.h" +#include "llstring.h" +#include "stringize.h" + +ScriptCommand::ScriptCommand(const std::string& command, const LLSD& path, + const std::string& base) +{ + fsyspath basepath(base); + // Use LLStringUtil::getTokens() to parse the script command line + args = LLStringUtil::getTokens(command, + " \t\r\n", // drop_delims + "", // no keep_delims + "\"'", // either kind of quotes + "\\"); // backslash escape + // search for args[0] on paths + if (search(args[0], path, basepath)) + { + // The first token is in fact the script filename. Now that we've + // found the script file, we've consumed that token. The rest are + // command-line arguments. + args.erase(args.begin()); + return; + } + + // Parsing the command line produced a script file path we can't find. + // Maybe that's because there are spaces in the original pathname that + // were neither quoted nor escaped? See if we can find the whole original + // command line string. + if (search(command, path, basepath)) + { + // Here we found script, using the whole input command line as its + // pathname. Discard any parts of it we mistook for command-line + // arguments. + args.clear(); + return; + } + + // Couldn't find the script either way. Is it because we can't even check + // existence? + if (! mError.empty()) + { + return; + } + + // No, existence check works, we just can't find the script. + std::ostringstream msgstream; + msgstream << "Can't find script file " << std::quoted(args[0]); + if (command != args[0]) + { + msgstream << " or " << std::quoted(command); + } + if (path.size() > 0) + { + msgstream << " on " << path; + } + if (! base.empty()) + { + msgstream << " relative to " << base; + } + mError = msgstream.str(); + LL_WARNS("Lua") << mError << LL_ENDL; +} + +bool ScriptCommand::search(const fsyspath& script, const LLSD& paths, const fsyspath& base) +{ + for (const auto& path : llsd::inArray(paths)) + { + // If a path is already absolute, (otherpath / path) preserves it. + // Explicitly instantiate fsyspath for every string conversion to + // properly convert UTF-8 filename strings on Windows. + fsyspath absscript{ base / fsyspath(path.asString()) / script }; + bool exists; + try + { + exists = std::filesystem::exists(absscript); + } + catch (const std::filesystem::filesystem_error& exc) + { + mError = stringize("Can't check existence: ", exc.what()); + LL_WARNS("Lua") << mError << LL_ENDL; + return false; + } + if (exists) + { + this->script = absscript.string(); + return true; + } + } + return false; +} + +std::string ScriptCommand::error() const +{ + return mError; +} diff --git a/indra/llcommon/scriptcommand.h b/indra/llcommon/scriptcommand.h new file mode 100644 index 0000000000..dafed552bb --- /dev/null +++ b/indra/llcommon/scriptcommand.h @@ -0,0 +1,64 @@ +/** + * @file scriptcommand.h + * @author Nat Goodspeed + * @date 2024-09-16 + * @brief ScriptCommand parses a string into a script filepath plus arguments. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_SCRIPTCOMMAND_H) +#define LL_SCRIPTCOMMAND_H + +#include "llsd.h" + +#include +#include + +class fsyspath; + +class ScriptCommand +{ +public: + /** + * ScriptCommand accepts a command-line string to invoke an existing + * script, which may or may not include arguments to pass to that script. + * The constructor parses the command line into tokens using quoting and + * escaping rules similar to bash. The first token is assumed to be the + * script name; ScriptCommand tries to find that script on the filesystem, + * trying each directory listed in the LLSD array of strings 'path'. When + * it finds the script file, it stores its full pathname in 'script' and + * the remaining tokens in 'args'. + * + * But if the constructor can't find the first token on 'path', it + * considers the possibility that the whole command-line string is + * actually a single pathname containing unescaped spaces. If that script + * file is found on 'path', it stores its full pathname in 'script' and + * leaves 'args' empty. + * + * We accept 'path' as an LLSD array rather than (e.g.) + * std::vector because the primary use case involves + * retrieving 'path' from settings. + * + * If you also pass 'base', any directory on 'path' may be specified + * relative to 'base'. Otherwise, every directory on 'path' must be + * absolute. + */ + ScriptCommand(const std::string& command, const LLSD& path=LLSD::emptyArray(), + const std::string& base={}); + + std::string script; + std::vector args; + + // returns empty string if no error + std::string error() const; + +private: + bool search(const fsyspath& script, const LLSD& path, const fsyspath& base); + + std::string mError; +}; + +#endif /* ! defined(LL_SCRIPTCOMMAND_H) */ diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 789aaab70d..a9ac48ac88 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1234,23 +1234,8 @@ bool LLAppViewer::init() "--luafile", "LuaScript", [](const LLSD& script) { - LLSD paths(gSavedSettings.getLLSD("LuaCommandPath")); - LL_DEBUGS("Lua") << "LuaCommandPath = " << paths << LL_ENDL; - for (const auto& path : llsd::inArray(paths)) - { - // if script path is already absolute, operator/() preserves it - auto abspath(fsyspath(gDirUtilp->getAppRODataDir()) / path.asString()); - auto absscript{ (abspath / script.asString()) }; - std::error_code ec; - if (std::filesystem::exists(absscript, ec)) - { - // no completion callback: we don't need to know - LLLUAmanager::runScriptFile(absscript.u8string()); - return; // from lambda - } - } - LL_WARNS("Lua") << "--luafile " << std::quoted(script.asString()) - << " not found on " << paths << LL_ENDL; + // no completion callback: we don't need to know + LLLUAmanager::runScriptFile(script); }); processComposeSwitch( "LuaAutorunPath", "LuaAutorunPath", @@ -1259,15 +1244,16 @@ bool LLAppViewer::init() // each directory can be relative to the viewer's install // directory -- if directory is already absolute, operator/() // preserves it - auto abspath(fsyspath(gDirUtilp->getAppRODataDir()) / directory.asString()); - std::string absdir(abspath.string()); + fsyspath abspath(fsyspath(gDirUtilp->getAppRODataDir()) / + fsyspath(directory.asString())); + std::string absdir(fsyspath(abspath).string()); LL_DEBUGS("InitInfo") << "LuaAutorunPath: " << absdir << LL_ENDL; LLDirIterator scripts(absdir, "*.lua"); std::string script; while (scripts.next(script)) { LL_DEBUGS("InitInfo") << "LuaAutorunPath: " << absdir << ": " << script << LL_ENDL; - LLLUAmanager::runScriptFile((abspath / script).string(), true); + LLLUAmanager::runScriptFile(fsyspath(abspath / fsyspath(script)).string(), true); } }); diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index c4ce07e492..046526ac3c 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -186,75 +186,41 @@ LLLUAmanager::script_result LLLUAmanager::waitScriptFile(const std::string& file } void LLLUAmanager::runScriptFile(const std::string &filename, bool autorun, - script_result_fn result_cb, script_finished_fn finished_cb) + script_result_fn result_cb) { // A script_result_fn will be called when LuaState::expr() completes. - LLCoros::instance().launch(filename, [filename, autorun, result_cb, finished_cb]() + LLCoros::instance().launch(filename, [filename, autorun, result_cb]() { ScriptObserver observer(LLCoros::getName(), filename); - // Use LLStringUtil::getTokens() to parse the script command line - auto tokens = LLStringUtil::getTokens(filename, - " \t\r\n", // drop_delims - "", // no keep_delims - "\"'", // either kind of quotes - "\\"); // backslash escape - -#error Under construction - // TODO: - // - Move this either/or file existence logic to LuaCommand() - // - Accept LuaCommand in all LLLUAmanager::mumbleScriptFile() methods - // - Update all existing mumbleScriptFile() callers - - llifstream in_file; - in_file.open(tokens[0]); - if (in_file.is_open()) - { - // The first token is in fact the script filename. Now that the - // script file is open, we've consumed that token. The rest are - // command-line arguments. - tokens.erase(tokens.begin()); - } - else + LLSD paths(gSavedSettings.getLLSD("LuaCommandPath")); + LL_DEBUGS("Lua") << "LuaCommandPath = " << paths << LL_ENDL; + // allow LuaCommandPath to be specified relative to install dir + ScriptCommand command(filename, paths, gDirUtilp->getAppRODataDir()); + auto error = command.error(); + if (! error.empty()) { - // Parsing the command line produced a script file path we can't - // open. Maybe that's because there are spaces in the original - // pathname that were neither quoted nor escaped? See if we can - // open the whole original command line string. - in_file.open(filename); - if (! in_file.is_open()) + if (result_cb) { - std::ostringstream msgstream; - msgstream << "Can't open script file " << std::quoted(tokens[0]); - if (filename != tokens[0]) - { - msgstream << " or " << std::quoted(filename); - } - auto msg = msgstream.str(); - LL_WARNS("Lua") << msg << LL_ENDL; - if (result_cb) - { - result_cb(-1, msg); - } - return; + result_cb(-1, error); } - // Here we do have in_file open, using the whole input command - // line as its pathname. Discard any parts of it we mistook for - // command-line arguments. - tokens.clear(); - } + return; + } - // Here in_file is open. + llifstream in_file; + in_file.open(command.script); + // At this point, since ScriptCommand did not report an error, we + // should be able to assume that 'script' exists. If we can't open it, + // something else is wrong?! + llassert(in_file.is_open()); if (autorun) { sAutorunScriptCount++; } sScriptCount++; - // A script_finished_fn is used to initialize the LuaState. - // It will be called when the LuaState is destroyed. - LuaState L(finished_cb); + LuaState L; std::string text{std::istreambuf_iterator(in_file), {}}; - auto [count, result] = L.expr(filename, text, tokens); + auto [count, result] = L.expr(command.script, text, command.args); if (result_cb) { result_cb(count, result); @@ -279,8 +245,7 @@ LLLUAmanager::script_result LLLUAmanager::waitScriptLine(const std::string& chun return startScriptLine(chunk).get(); } -void LLLUAmanager::runScriptLine(const std::string& chunk, script_result_fn result_cb, - script_finished_fn finished_cb) +void LLLUAmanager::runScriptLine(const std::string& chunk, script_result_fn result_cb) { // find a suitable abbreviation for the chunk string std::string shortchunk{ chunk }; @@ -292,11 +257,9 @@ void LLLUAmanager::runScriptLine(const std::string& chunk, script_result_fn resu shortchunk = stringize(shortchunk.substr(0, shortlen), "..."); std::string desc{ "lua: " + shortchunk }; - LLCoros::instance().launch(desc, [desc, chunk, result_cb, finished_cb]() + LLCoros::instance().launch(desc, [desc, chunk, result_cb]() { - // A script_finished_fn is used to initialize the LuaState. - // It will be called when the LuaState is destroyed. - LuaState L(finished_cb); + LuaState L; auto [count, result] = L.expr(desc, chunk); if (result_cb) { diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h index df76ddd3e2..b98b5d4ef6 100644 --- a/indra/newview/llluamanager.h +++ b/indra/newview/llluamanager.h @@ -44,9 +44,6 @@ class LLLUAmanager friend class ScriptObserver; 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 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. @@ -58,20 +55,31 @@ public: // same semantics as script_result_fn parameters typedef std::pair script_result; - static void runScriptFile(const std::string &filename, bool autorun = false, script_result_fn result_cb = {}, - script_finished_fn finished_cb = {}); + // Run the script specified by the command line passed as @a filename. + // This can be followed by some number of command-line arguments, which + // a Lua script can view using either '...' or predefined global 'arg'. + // The script pathname or its arguments can be quoted using 'single + // quotes' or "double quotes", or special characters can be \escaped. + // runScriptFile() recognizes the case in which the whole 'filename' + // string is a path containing spaces; if so no arguments are permitted. + // In either form, if the script pathname isn't absolute, it is sought on + // LuaCommandPath. + // If autorun is true, statistics will count this as an autorun script. + static void runScriptFile(const std::string &filename, bool autorun = false, + script_result_fn result_cb = {}); // Start running a Lua script file, returning an LLCoros::Future whose // get() method will pause the calling coroutine until it can deliver the // (count, result) pair described above. Between startScriptFile() and // Future::get(), the caller and the Lua script coroutine will run // concurrently. + // @a filename is as described for runScriptFile(). static LLCoros::Future startScriptFile(const std::string& filename); // Run a Lua script file, and pause the calling coroutine until it completes. // The return value is the (count, result) pair described above. + // @a filename is as described for runScriptFile(). static script_result waitScriptFile(const std::string& filename); - static void runScriptLine(const std::string &chunk, script_result_fn result_cb = {}, - script_finished_fn finished_cb = {}); + static void runScriptLine(const std::string &chunk, script_result_fn result_cb = {}); // Start running a Lua chunk, returning an LLCoros::Future whose // get() method will pause the calling coroutine until it can deliver the // (count, result) pair described above. Between startScriptLine() and -- cgit v1.2.3 From 5545505d0461726b7a617684fc4a4e2692469f6d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 20 Sep 2024 16:34:38 -0400 Subject: Update test for changed message in error case. --- indra/llcommon/tests/llleap_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index ca1939c81e..a345608299 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -300,8 +300,8 @@ namespace tut std::string threw = catch_what([&BADPYTHON](){ LLLeap::create("bad exe", BADPYTHON); }); - ensure_contains("LLLeap::create() didn't throw", threw, "failed"); - log.messageWith("failed"); + ensure_contains("LLLeap::create() didn't throw", threw, "Can't find"); + log.messageWith("Can't find"); log.messageWith(BADPYTHON); // try the suppress-exception variant ensure("bad launch returned non-NULL", ! LLLeap::create("bad exe", BADPYTHON, false)); -- cgit v1.2.3 From 65379a3b1784209193fda64fa6e99cfb9b87c340 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 20 Sep 2024 16:39:13 -0400 Subject: Fix trailing spaces. --- indra/newview/llluamanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 046526ac3c..7fe5c1ece0 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -204,7 +204,7 @@ void LLLUAmanager::runScriptFile(const std::string &filename, bool autorun, result_cb(-1, error); } return; - } + } llifstream in_file; in_file.open(command.script); -- cgit v1.2.3 From 8efba1db81fc1294114cea70de0df53f4d4ab9a4 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 23 Sep 2024 10:23:37 -0400 Subject: Use Lua command-line args to make frame_profile_quit.lua generic. Now the location to which to teleport and the camera focus point can both be specified by the caller, in this case the frame_profile bash script. --- indra/newview/scripts/lua/frame_profile_quit.lua | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/newview/scripts/lua/frame_profile_quit.lua b/indra/newview/scripts/lua/frame_profile_quit.lua index e3177a3f67..ad136c86d2 100644 --- a/indra/newview/scripts/lua/frame_profile_quit.lua +++ b/indra/newview/scripts/lua/frame_profile_quit.lua @@ -1,5 +1,11 @@ -- Trigger Develop -> Render Tests -> Frame Profile and quit +assert(arg.n == 3, 'Usage: frame_profile_quit.lua x y z (for camera focus)') +-- Try coercing string arguments to numbers, using Lua's implicit conversion. +-- If the args passed as x, y, z won't convert nicely to numbers, better find +-- out now. +focus = {arg[1]+0, arg[2]+0, arg[3]+0} + LLAgent = require 'LLAgent' logout = require 'logout' startup = require 'startup' @@ -8,11 +14,14 @@ UI = require 'UI' startup.wait('STATE_STARTED') --- Assume we logged into http://maps.secondlife.com/secondlife/Bug%20Island/220/224/27 --- (see frame_profile bash script) +-- Figure out where we are +camera = LLAgent.getRegionPosition() +-- assume z is the agent's feet, add 2 meters +camera[2] += 2 + Timer(10, 'wait') -LLAgent.setCamera{camera_pos={220, 224, 26}, camera_locked=true, - focus_pos ={228, 232, 26}, focus_locked=true} +LLAgent.setCamera{camera_pos=camera, camera_locked=true, + focus_pos=focus, focus_locked=true} Timer(1, 'wait') -- This freezes the viewer for perceptible realtime UI.popup:tip('starting Render Tests -> Frame Profile') -- cgit v1.2.3 From e290d14f65946600939321d2471ce9d4b2508e3e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 23 Sep 2024 17:02:12 -0400 Subject: include for Linux: std::forward is not predefined --- indra/llcommon/always_return.h | 1 + indra/llcommon/llexception.h | 1 + 2 files changed, 2 insertions(+) (limited to 'indra') diff --git a/indra/llcommon/always_return.h b/indra/llcommon/always_return.h index b99eb49096..a56a8b443e 100644 --- a/indra/llcommon/always_return.h +++ b/indra/llcommon/always_return.h @@ -14,6 +14,7 @@ #define LL_ALWAYS_RETURN_H #include // std::enable_if, std::is_convertible +#include // std::forward namespace LL { diff --git a/indra/llcommon/llexception.h b/indra/llcommon/llexception.h index ba225ad221..0c32a55409 100644 --- a/indra/llcommon/llexception.h +++ b/indra/llcommon/llexception.h @@ -15,6 +15,7 @@ #include "always_return.h" #include "stdtypes.h" #include +#include // std::forward #include #include #include -- cgit v1.2.3