summaryrefslogtreecommitdiff
path: root/indra/newview
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/newview
parentc8f7e9d0256cec90d509b0cf0109c2c7479100d0 (diff)
parentf069543328efc441673db9877c97ae2201b86e91 (diff)
Merge branch 'release/luau-scripting' into release/luau-scripting
Diffstat (limited to 'indra/newview')
-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
11 files changed, 269 insertions, 120 deletions
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