summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorNicky Dasmijn <nicky.dasmijn@posteo.nl>2024-04-03 21:36:43 +0200
committerGitHub <noreply@github.com>2024-04-03 21:36:43 +0200
commit6b8f86885b500555ec4f2bb06db8fc9020b723cd (patch)
tree41a91394ea2d30164fb674ffe3e10c6f26250a6c /indra
parentc8f7e9d0256cec90d509b0cf0109c2c7479100d0 (diff)
parentf069543328efc441673db9877c97ae2201b86e91 (diff)
Merge branch 'release/luau-scripting' into release/luau-scripting
Diffstat (limited to 'indra')
-rw-r--r--indra/llcommon/CMakeLists.txt1
-rw-r--r--indra/llcommon/fsyspath.h74
-rw-r--r--indra/llcommon/llleaplistener.cpp46
-rw-r--r--indra/llcommon/llstring.h5
-rw-r--r--indra/llcommon/lua_function.cpp61
-rw-r--r--indra/llcommon/lua_function.h3
-rw-r--r--indra/llcommon/lualistener.cpp1
-rw-r--r--indra/llui/llluafloater.cpp8
-rw-r--r--indra/newview/llluamanager.cpp52
-rw-r--r--indra/newview/llluamanager.h5
-rw-r--r--indra/newview/scripts/lua/ErrorQueue.lua8
-rw-r--r--indra/newview/scripts/lua/Queue.lua6
-rw-r--r--indra/newview/scripts/lua/WaitQueue.lua11
-rw-r--r--indra/newview/scripts/lua/fiber.lua45
-rw-r--r--indra/newview/scripts/lua/leap.lua99
-rw-r--r--indra/newview/scripts/lua/startup.lua101
-rw-r--r--indra/newview/scripts/lua/test_luafloater_demo.lua25
-rw-r--r--indra/newview/scripts/lua/test_luafloater_gesture_list.lua26
-rw-r--r--indra/newview/scripts/lua/util.lua11
19 files changed, 439 insertions, 149 deletions
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 <filesystem>
+
+// 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<char8_t> 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/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp
index 55e4752c5d..9b9b0f5121 100644
--- a/indra/llcommon/llleaplistener.cpp
+++ b/indra/llcommon/llleaplistener.cpp
@@ -70,44 +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"
- "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.",
+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<void (LLLeapListener::*)(const LLSD&) const>(&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()));
}
@@ -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);
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 <algorithm>
#include <vector>
#include <map>
+#include <type_traits>
#include "llformat.h"
#include "stdtypes.h"
@@ -542,7 +543,7 @@ public:
template <typename TO>
inline operator TO() const
{
- return ll_convert_impl<TO, FROM>()(mRef);
+ return ll_convert_impl<TO, std::decay_t<const FROM>>()(mRef);
}
};
@@ -551,7 +552,7 @@ public:
template<typename TO, typename FROM>
TO ll_convert_to(const FROM& in)
{
- return ll_convert_impl<TO, FROM>()(in);
+ return ll_convert_impl<TO, std::decay_t<const FROM>>()(in);
}
// degenerate case
diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp
index e731408c7d..7a5668f384 100644
--- a/indra/llcommon/lua_function.cpp
+++ b/indra/llcommon/lua_function.cpp
@@ -17,13 +17,13 @@
// std headers
#include <algorithm>
#include <exception>
-#include <filesystem>
#include <iomanip> // std::quoted
#include <map>
#include <memory> // std::unique_ptr
#include <typeinfo>
// external library headers
// other Linden headers
+#include "fsyspath.h"
#include "hexdump.h"
#include "lleventcoro.h"
#include "llsd.h"
@@ -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);
}
+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
+ 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<int, LLSD> 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<int, LLSD> 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<int, LLSD> 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<int, LLSD> 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()) };
@@ -560,7 +577,7 @@ std::pair<int, LLSD> 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;
@@ -577,9 +594,13 @@ std::pair<int, LLSD> 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
@@ -685,6 +706,38 @@ std::pair<LuaFunction::Registry&, LuaFunction::Lookup&> LuaFunction::getState()
}
/*****************************************************************************
+* 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).u8string());
+ 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().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;
+}
+
+/*****************************************************************************
* check_stop()
*****************************************************************************/
lua_function(check_stop, "ensure that a Lua script responds to viewer shutdown")
diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h
index 868c13c3f1..ec1e6cdb10 100644
--- a/indra/llcommon/lua_function.h
+++ b/indra/llcommon/lua_function.h
@@ -16,6 +16,7 @@
#include "luau/lua.h"
#include "luau/luaconf.h"
#include "luau/lualib.h"
+#include "fsyspath.h"
#include "stringize.h"
#include <exception> // std::uncaught_exceptions()
#include <memory> // std::shared_ptr
@@ -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);
+
+ fsyspath source_path(lua_State* L);
} // namespace lluau
std::string lua_tostdstring(lua_State* L, int index);
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&)
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 <filesystem>
+#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 be332a7244..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 <boost/algorithm/string/replace.hpp>
-#include <filesystem>
#include "luau/luacode.h"
#include "luau/lua.h"
@@ -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(fsyspath(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,30 +368,30 @@ 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<std::string> lib_paths
+ std::vector<fsyspath> 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
};
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;
@@ -457,12 +450,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
@@ -472,7 +467,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)
{
@@ -494,9 +491,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);
}
diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h
index a297d14502..3c00450179 100644
--- a/indra/newview/llluamanager.h
+++ b/indra/newview/llluamanager.h
@@ -27,6 +27,7 @@
#ifndef LL_LLLUAMANAGER_H
#define LL_LLLUAMANAGER_H
+#include "fsyspath.h"
#include "llcoros.h"
#include "llsd.h"
#include <functional>
@@ -88,8 +89,8 @@ class LLRequireResolver
static void resolveRequire(lua_State *L, std::string path);
private:
- std::string mPathToResolve;
- std::string mSourceChunkname;
+ fsyspath mPathToResolve;
+ fsyspath mSourceDir;
LLRequireResolver(lua_State *L, const std::string& path);
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/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()
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..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 debug = require('printf')
-local function debug(...) end
+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,16 +107,15 @@ end
--
-- See also request(), generate().
function leap.send(pump, data, reqid)
- debug('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
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
@@ -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.
- debug('requestSetup(%s, %s)', pump, 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]
- debug('leap.request(%s, %s) about to wait on %s', pump, data, tostring(waitfor))
+ 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)
- 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
+ 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,8 +212,8 @@ 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
- debug('unsolicited() checking %s', waitfor.name)
+ for i, waitfor in pairs(waitfors) do
+ dbg('unsolicited() checking %s', waitfor.name)
if waitfor:handle(pump, data) then
return
end
@@ -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
@@ -243,9 +246,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)
@@ -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
@@ -368,9 +371,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
@@ -389,10 +392,10 @@ 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)
- 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
@@ -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
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..ab638dcdd1 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.abspath("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 {<control_name>= {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..3d9a9b0ad4 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.abspath("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 {<control_name>= {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
diff --git a/indra/newview/scripts/lua/util.lua b/indra/newview/scripts/lua/util.lua
index e3af633ea7..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