diff options
Diffstat (limited to 'indra/newview')
-rw-r--r-- | indra/newview/llluamanager.cpp | 52 | ||||
-rw-r--r-- | indra/newview/llluamanager.h | 5 | ||||
-rw-r--r-- | indra/newview/scripts/lua/ErrorQueue.lua | 8 | ||||
-rw-r--r-- | indra/newview/scripts/lua/Queue.lua | 6 | ||||
-rw-r--r-- | indra/newview/scripts/lua/WaitQueue.lua | 11 | ||||
-rw-r--r-- | indra/newview/scripts/lua/fiber.lua | 45 | ||||
-rw-r--r-- | indra/newview/scripts/lua/leap.lua | 99 | ||||
-rw-r--r-- | indra/newview/scripts/lua/startup.lua | 101 | ||||
-rw-r--r-- | indra/newview/scripts/lua/test_luafloater_demo.lua | 25 | ||||
-rw-r--r-- | indra/newview/scripts/lua/test_luafloater_gesture_list.lua | 26 | ||||
-rw-r--r-- | indra/newview/scripts/lua/util.lua | 11 |
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 |