From 76d36fe7210443b4564d2646a2fe5aa6d7c490fc Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Mon, 29 Apr 2024 19:56:41 +0300 Subject: Allow getting the value of debug settings via Lua script --- indra/newview/scripts/lua/LLDebugSettings.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 indra/newview/scripts/lua/LLDebugSettings.lua (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/LLDebugSettings.lua b/indra/newview/scripts/lua/LLDebugSettings.lua new file mode 100644 index 0000000000..71a12a2ca2 --- /dev/null +++ b/indra/newview/scripts/lua/LLDebugSettings.lua @@ -0,0 +1,13 @@ +leap = require 'leap' + +local LLDebugSettings = {} + +function LLDebugSettings.set(name, value) + leap.send('LLAppViewer', {op='setDebugSetting', setting=name, value=value}) +end + +function LLDebugSettings.get(name) + return leap.request('LLAppViewer', {op='getDebugSetting', setting=name})['value'] +end + +return LLDebugSettings -- cgit v1.2.3 From 79dca07d790a47db192099bcb85e740676a643ee Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Wed, 1 May 2024 19:58:47 +0300 Subject: Use LLViewerControlListener to access debug settings --- indra/newview/scripts/lua/LLDebugSettings.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/LLDebugSettings.lua b/indra/newview/scripts/lua/LLDebugSettings.lua index 71a12a2ca2..80ae7e87bb 100644 --- a/indra/newview/scripts/lua/LLDebugSettings.lua +++ b/indra/newview/scripts/lua/LLDebugSettings.lua @@ -3,11 +3,15 @@ leap = require 'leap' local LLDebugSettings = {} function LLDebugSettings.set(name, value) - leap.send('LLAppViewer', {op='setDebugSetting', setting=name, value=value}) + return leap.request('LLViewerControl', {op='set', group='Global', key=name, value=value}) +end + +function LLDebugSettings.toggle(name) + return leap.request('LLViewerControl', {op='toggle', group='Global', key=name}) end function LLDebugSettings.get(name) - return leap.request('LLAppViewer', {op='getDebugSetting', setting=name})['value'] + return leap.request('LLViewerControl', {op='get', group='Global', key=name})['value'] end return LLDebugSettings -- cgit v1.2.3 From f9a2748b542d05d153fa591a8c452162d9402828 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Thu, 2 May 2024 14:03:34 +0300 Subject: Raise Lua error if LLViewerControlListener response contains one --- indra/newview/scripts/lua/LLDebugSettings.lua | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/LLDebugSettings.lua b/indra/newview/scripts/lua/LLDebugSettings.lua index 80ae7e87bb..c809dfff91 100644 --- a/indra/newview/scripts/lua/LLDebugSettings.lua +++ b/indra/newview/scripts/lua/LLDebugSettings.lua @@ -1,17 +1,24 @@ leap = require 'leap' +function check_response(res) + if res.error then + error(res.error) + end + return res +end + local LLDebugSettings = {} function LLDebugSettings.set(name, value) - return leap.request('LLViewerControl', {op='set', group='Global', key=name, value=value}) + check_response(leap.request('LLViewerControl', {op='set', group='Global', key=name, value=value})) end function LLDebugSettings.toggle(name) - return leap.request('LLViewerControl', {op='toggle', group='Global', key=name}) + check_response(leap.request('LLViewerControl', {op='toggle', group='Global', key=name})) end function LLDebugSettings.get(name) - return leap.request('LLViewerControl', {op='get', group='Global', key=name})['value'] + return check_response(leap.request('LLViewerControl', {op='get', group='Global', key=name}))['value'] end return LLDebugSettings -- cgit v1.2.3 From cec3b8d870085925cd0c9fb900b7d5e4629bcbfd Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Tue, 7 May 2024 13:10:00 +0300 Subject: Copy xml files to scripts/lua; make Lua debug floater resizable --- indra/newview/scripts/lua/LLDebugSettings.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/LLDebugSettings.lua b/indra/newview/scripts/lua/LLDebugSettings.lua index c809dfff91..c1d74fe00a 100644 --- a/indra/newview/scripts/lua/LLDebugSettings.lua +++ b/indra/newview/scripts/lua/LLDebugSettings.lua @@ -1,6 +1,6 @@ leap = require 'leap' -function check_response(res) +local function check_response(res) if res.error then error(res.error) end -- cgit v1.2.3 From bd8e1dd8d2340636521200d15a045321ea07b986 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 8 May 2024 16:58:34 -0400 Subject: Tweak a couple things --- indra/newview/scripts/lua/LLDebugSettings.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/LLDebugSettings.lua b/indra/newview/scripts/lua/LLDebugSettings.lua index c809dfff91..c1d74fe00a 100644 --- a/indra/newview/scripts/lua/LLDebugSettings.lua +++ b/indra/newview/scripts/lua/LLDebugSettings.lua @@ -1,6 +1,6 @@ leap = require 'leap' -function check_response(res) +local function check_response(res) if res.error then error(res.error) end -- cgit v1.2.3 From 1abf5f18d6afc7ae9e1b1562b92e5c1ce33b722f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 15 May 2024 09:03:02 -0400 Subject: Make leap.lua honor an "error" key in viewer response. --- indra/newview/scripts/lua/leap.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/leap.lua b/indra/newview/scripts/lua/leap.lua index ade91789f0..cfb7377523 100644 --- a/indra/newview/scripts/lua/leap.lua +++ b/indra/newview/scripts/lua/leap.lua @@ -161,10 +161,12 @@ function leap.request(pump, data) dbg('leap.request(%s, %s) got %s: %s', pump, data, ok, response) -- kill off temporary WaitForReqid object, even if error pending[reqid] = nil - if ok then - return response - else + if not ok then error(response) + elseif response.error then + error(response.error) + else + return response end end @@ -186,7 +188,7 @@ function leap.generate(pump, data, checklast) local ok, response, resumed_with repeat ok, response = pcall(waitfor.wait, waitfor) - if not ok then + if (not ok) or response.error then break end -- can resume(false) to terminate generate() and clean up @@ -196,6 +198,8 @@ function leap.generate(pump, data, checklast) pending[reqid] = nil if not ok then error(response) + elseif response.error then + error(response.error) end end -- cgit v1.2.3 From 9f540f10e687bb3889de191afbae3b52cc21f415 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Wed, 15 May 2024 18:50:51 +0300 Subject: Add trusted flag to UI callbacks, so not everything is accessible from the script --- indra/newview/scripts/lua/LLDebugSettings.lua | 14 ++++---------- indra/newview/scripts/lua/UI.lua | 5 +++-- indra/newview/scripts/lua/util.lua | 7 +++++++ 3 files changed, 14 insertions(+), 12 deletions(-) (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/LLDebugSettings.lua b/indra/newview/scripts/lua/LLDebugSettings.lua index c1d74fe00a..06a8a63727 100644 --- a/indra/newview/scripts/lua/LLDebugSettings.lua +++ b/indra/newview/scripts/lua/LLDebugSettings.lua @@ -1,24 +1,18 @@ leap = require 'leap' - -local function check_response(res) - if res.error then - error(res.error) - end - return res -end +util = require 'util' local LLDebugSettings = {} function LLDebugSettings.set(name, value) - check_response(leap.request('LLViewerControl', {op='set', group='Global', key=name, value=value})) + util.check_response(leap.request('LLViewerControl', {op='set', group='Global', key=name, value=value})) end function LLDebugSettings.toggle(name) - check_response(leap.request('LLViewerControl', {op='toggle', group='Global', key=name})) + util.check_response(leap.request('LLViewerControl', {op='toggle', group='Global', key=name})) end function LLDebugSettings.get(name) - return check_response(leap.request('LLViewerControl', {op='get', group='Global', key=name}))['value'] + return util.check_response(leap.request('LLViewerControl', {op='get', group='Global', key=name}))['value'] end return LLDebugSettings diff --git a/indra/newview/scripts/lua/UI.lua b/indra/newview/scripts/lua/UI.lua index f851632bad..6101c7a312 100644 --- a/indra/newview/scripts/lua/UI.lua +++ b/indra/newview/scripts/lua/UI.lua @@ -1,16 +1,17 @@ -- Engage the UI LLEventAPI leap = require 'leap' +util = require 'util' local UI = {} function UI.call(func, parameter) -- 'call' is fire-and-forget - leap.send('UI', {op='call', ['function']=func, parameter=parameter}) + util.check_response(leap.request('UI', {op='call', ['function']=func, parameter=parameter})) end function UI.getValue(path) - return leap.request('UI', {op='getValue', path=path})['value'] + return util.check_response(leap.request('UI', {op='getValue', path=path}))['value'] end return UI diff --git a/indra/newview/scripts/lua/util.lua b/indra/newview/scripts/lua/util.lua index a2191288f6..404efdf09e 100644 --- a/indra/newview/scripts/lua/util.lua +++ b/indra/newview/scripts/lua/util.lua @@ -41,4 +41,11 @@ function util.equal(t1, t2) return util.empty(temp) end +function util.check_response(res) + if res.error then + error(res.error) + end + return res +end + return util -- cgit v1.2.3 From 0f00dc2f658869cc73a18b03b024a6cc88964e0b Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Wed, 22 May 2024 20:29:55 +0300 Subject: Add support for sending messages to Nearby chat from Lua script --- indra/newview/scripts/lua/LLChat.lua | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 indra/newview/scripts/lua/LLChat.lua (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/LLChat.lua b/indra/newview/scripts/lua/LLChat.lua new file mode 100644 index 0000000000..7db538e837 --- /dev/null +++ b/indra/newview/scripts/lua/LLChat.lua @@ -0,0 +1,17 @@ +leap = require 'leap' + +local LLChat = {} + +function LLChat.sendNearby(msg) + leap.send('LLChatBar', {op='sendChat', message=msg}) +end + +function LLChat.sendWhisper(msg) + leap.send('LLChatBar', {op='sendChat', type='whisper', message=msg}) +end + +function LLChat.sendShout(msg) + leap.send('LLChatBar', {op='sendChat', type='shout', message=msg}) +end + +return LLChat -- cgit v1.2.3 From 85664b011e37c6c9926924f6fb72ceeb10d07833 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Wed, 22 May 2024 21:03:45 +0300 Subject: add throttle for sending messages; add simple demo script --- indra/newview/scripts/lua/test_LLChat.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 indra/newview/scripts/lua/test_LLChat.lua (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/test_LLChat.lua b/indra/newview/scripts/lua/test_LLChat.lua new file mode 100644 index 0000000000..95bd218baa --- /dev/null +++ b/indra/newview/scripts/lua/test_LLChat.lua @@ -0,0 +1,18 @@ +LLChat = require 'LLChat' + +function generateRandomWord(length) + local alphabet = "abcdefghijklmnopqrstuvwxyz" + local word = "" + for i = 1, length do + local randomIndex = math.random(1, #alphabet) + word = word .. alphabet:sub(randomIndex, randomIndex) + end + return word +end + +local msg = "" +math.randomseed(os.time()) +for i = 1, math.random(1, 10) do + msg = msg .. " ".. generateRandomWord(math.random(1, 8)) +end +LLChat.sendNearby(msg) -- cgit v1.2.3 From e4b7d2f463d19cab28985414365fd8415e3dc700 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 24 May 2024 15:24:50 +0300 Subject: Mark script messages in compact mode too; code clean up --- indra/newview/scripts/lua/test_LLChat.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/test_LLChat.lua b/indra/newview/scripts/lua/test_LLChat.lua index 95bd218baa..883b82fafb 100644 --- a/indra/newview/scripts/lua/test_LLChat.lua +++ b/indra/newview/scripts/lua/test_LLChat.lua @@ -2,15 +2,15 @@ LLChat = require 'LLChat' function generateRandomWord(length) local alphabet = "abcdefghijklmnopqrstuvwxyz" - local word = "" + local wordTable = {} for i = 1, length do local randomIndex = math.random(1, #alphabet) - word = word .. alphabet:sub(randomIndex, randomIndex) + table.insert(wordTable, alphabet:sub(randomIndex, randomIndex)) end - return word + return table.concat(wordTable) end -local msg = "" +local msg = "AI says:" math.randomseed(os.time()) for i = 1, math.random(1, 10) do msg = msg .. " ".. generateRandomWord(math.random(1, 8)) -- cgit v1.2.3 From b0ef843fe0702482e843379b4975b2dda4d58935 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 24 May 2024 09:22:40 -0400 Subject: Nat's ideas from PR #1547 --- indra/newview/scripts/lua/test_LLChat.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/test_LLChat.lua b/indra/newview/scripts/lua/test_LLChat.lua index 95bd218baa..3c5cbeeeb2 100644 --- a/indra/newview/scripts/lua/test_LLChat.lua +++ b/indra/newview/scripts/lua/test_LLChat.lua @@ -2,17 +2,17 @@ LLChat = require 'LLChat' function generateRandomWord(length) local alphabet = "abcdefghijklmnopqrstuvwxyz" - local word = "" + local word = {} for i = 1, length do local randomIndex = math.random(1, #alphabet) - word = word .. alphabet:sub(randomIndex, randomIndex) + table.insert(word, alphabet:sub(randomIndex, randomIndex)) end - return word + return table.concat(word) end -local msg = "" +local msg = {'AI says:'} math.randomseed(os.time()) for i = 1, math.random(1, 10) do - msg = msg .. " ".. generateRandomWord(math.random(1, 8)) + table.insert(msg, generateRandomWord(math.random(1, 8))) end -LLChat.sendNearby(msg) +LLChat.sendNearby(table.concat(msg, ' ')) -- cgit v1.2.3 From d61831cabb8c63677e3684c8ba12b24b66923aa9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 31 May 2024 11:17:48 -0400 Subject: Don't check if printf(format) arg is a string. --- indra/newview/scripts/lua/printf.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/printf.lua b/indra/newview/scripts/lua/printf.lua index 584cd4f391..e84b2024df 100644 --- a/indra/newview/scripts/lua/printf.lua +++ b/indra/newview/scripts/lua/printf.lua @@ -2,7 +2,7 @@ local inspect = require 'inspect' -local function printf(...) +local function printf(format, ...) -- string.format() only handles numbers and strings. -- Convert anything else to string using the inspect module. local args = {} @@ -13,7 +13,7 @@ local function printf(...) table.insert(args, inspect(arg)) end end - print(string.format(table.unpack(args))) + print(string.format(format, table.unpack(args))) end return printf -- cgit v1.2.3 From e352192045cfe23a681dcaba71d94311f42e230f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 31 May 2024 11:19:58 -0400 Subject: Add a bit more dbg() conditional diagnostic output. --- indra/newview/scripts/lua/fiber.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/fiber.lua b/indra/newview/scripts/lua/fiber.lua index 9057e6c890..cae27b936b 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 dbg = printf local function dbg(...) end +-- local dbg = printf local coro = require 'coro' local fiber = {} @@ -303,6 +303,8 @@ function fiber.yield() end -- We're ready! Just return to caller. In this situation we don't care -- whether there are other ready fibers. + dbg('fiber.yield() returning to %s (%sothers are ready)', + fiber.get_name(), ((not others) and "no " or "")) end -- Run fibers until all but main have terminated: return nil. -- cgit v1.2.3 From c59f8bc59ad958d169a7626739b2b81439180537 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 31 May 2024 11:35:30 -0400 Subject: Add leap.eventstream() and cancelreq() functions. leap.eventstream() is used when we expect the viewer's LLEventAPI to send an immediate first response with the reqid from the request, followed by some number of subsequent responses bearing the same reqid. The difference between eventstream() and generate() is that generate() expects the caller to request each such response, whereas eventstream calls the caller's callback with each response. cancelreq() is for canceling the background fiber launched by eventstream() before the callback tells it to quit. Make WaitFor:close() remove the object from the waitfors list; similarly, make WaitForReqid:close() remove the object from the pending list. For this reason, cleanup() must iterate over a copy of each of the pending and waitfors lists. Instead of unregisterWaitFor() manually searching the waitfors list, use table.find(). --- indra/newview/scripts/lua/leap.lua | 106 ++++++++++++++++++++++++++++++++----- 1 file changed, 93 insertions(+), 13 deletions(-) (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/leap.lua b/indra/newview/scripts/lua/leap.lua index cfb7377523..69cedb4c9e 100644 --- a/indra/newview/scripts/lua/leap.lua +++ b/indra/newview/scripts/lua/leap.lua @@ -40,6 +40,7 @@ local fiber = require('fiber') local ErrorQueue = require('ErrorQueue') +local inspect = require('inspect') local function dbg(...) end -- local dbg = require('printf') @@ -74,7 +75,7 @@ local reply, command = LL.get_event_pumps() -- 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 = {} +local 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. @@ -82,7 +83,7 @@ pending = {} -- 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) +local 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 @@ -131,7 +132,7 @@ local function requestSetup(pump, data) local waitfor = leap.WaitForReqid:new(reqid) pending[reqid] = waitfor -- Pass reqid to send() to stamp it into (a copy of) the request data. - dbg('requestSetup(%s, %s)', pump, data) + dbg('requestSetup(%s, %s) storing %s', pump, data, waitfor.name) leap.send(pump, data, reqid) return reqid, waitfor end @@ -177,7 +178,7 @@ end -- -- If you pass checklast=, each response event is -- passed to that callable (after the yield). When the callable returns --- True, the generator terminates in the usual way. +-- true, the generator terminates in the usual way. -- -- See request() remarks about ["reqid"]. function leap.generate(pump, data, checklast) @@ -203,12 +204,80 @@ function leap.generate(pump, data, checklast) end end +-- Send the specified request LLSD, expecting an immediate reply followed by +-- an arbitrary number of subsequent replies with the same reqid. Block the +-- calling coroutine until the first (immediate) reply, but launch a separate +-- fiber on which to call the passed callback with later replies. +-- +-- Once the callback returns true, the background fiber terminates. +function leap.eventstream(pump, data, callback) + local reqid, waitfor = requestSetup(pump, data) + local response = waitfor:wait() + if response.error then + -- clean up our WaitForReqid + waitfor:close() + error(response.error) + end + -- No error, so far so good: + -- call the callback with the first response just in case + local ok, done = pcall(callback, response) + if not ok then + -- clean up our WaitForReqid + waitfor:close() + error(done) + end + if done then + return response + end + -- callback didn't throw an error, and didn't say stop, + -- so set up to handle subsequent events + -- TODO: distinguish "daemon" fibers that can be terminated even if waiting + fiber.launch( + pump, + function () + local ok, done + repeat + event = waitfor:wait() + if not event then + -- wait() returns nil once the queue is closed (e.g. cancelreq()) + ok, done = true, true + else + ok, done = pcall(callback, event) + end + -- not ok means callback threw an error (caught as 'done') + -- done means callback succeeded but wants to stop + until (not ok) or done + -- once we break this loop, clean up our WaitForReqid + waitfor:close() + if not ok then + -- can't reflect the error back to our caller + LL.print_warning(fiber.get_name() .. ': ' .. done) + end + end) + return response +end + +-- we might want to clean up after leap.eventstream() even if the callback has +-- not yet returned true +function leap.cancelreq(reqid) + dbg('cancelreq(%s)', reqid) + local waitfor = pending[reqid] + if waitfor ~= nil then + -- close() removes the pending entry and also closes the queue, + -- breaking the background fiber's wait loop. + dbg('cancelreq(%s) canceling %s', reqid, waitfor.name) + waitfor:close() + end +end + local function cleanup(message) - -- we're done: clean up all pending coroutines - for i, waitfor in pairs(pending) do + -- We're done: clean up all pending coroutines. + -- Iterate over copies of the pending and waitfors tables, since the + -- close() operation modifies the real tables. + for i, waitfor in pairs(table.clone(pending)) do waitfor:close() end - for i, waitfor in pairs(waitfors) do + for i, waitfor in pairs(table.clone(waitfors)) do waitfor:close() end end @@ -223,7 +292,8 @@ local function unsolicited(pump, data) return end end - LL.print_debug(string.format('unsolicited(%s, %s) discarding unclaimed event', pump, data)) + LL.print_debug(string.format('unsolicited(%s, %s) discarding unclaimed event', + pump, inspect(data))) end -- Route incoming (pump, data) event to the appropriate waiting coroutine. @@ -231,14 +301,17 @@ local function dispatch(pump, data) local reqid = data['reqid'] -- if the response has no 'reqid', it's not from request() or generate() if reqid == nil then +-- dbg('dispatch() found no reqid; calling unsolicited(%s, %s)', pump, data) return unsolicited(pump, data) end -- have reqid; do we have a WaitForReqid? local waitfor = pending[reqid] if waitfor == nil then +-- dbg('dispatch() found no WaitForReqid(%s); calling unsolicited(%s, %s)', reqid, pump, data) return unsolicited(pump, data) end -- found the right WaitForReqid object, let it handle the event +-- dbg('dispatch() calling %s.handle(%s, %s)', waitfor.name, pump, data) waitfor:handle(pump, data) end @@ -284,11 +357,9 @@ end -- called by WaitFor.disable() local function unregisterWaitFor(waitfor) - for i, w in pairs(waitfors) do - if w == waitfor then - waitfors[i] = nil - break - end + local i = table.find(waitfors, waitfor) + if i ~= nil then + waitfors[i] = nil end end @@ -417,6 +488,7 @@ end -- called by cleanup() at end function leap.WaitFor:close() + self:disable() self._queue:close() end @@ -437,6 +509,8 @@ function leap.WaitForReqid:new(reqid) setmetatable(obj, self) self.__index = self + obj.reqid = reqid + return obj end @@ -447,4 +521,10 @@ function leap.WaitForReqid:filter(pump, data) return data end +function leap.WaitForReqid:close() + -- remove this entry from pending table + pending[self.reqid] = nil + self._queue:close() +end + return leap -- cgit v1.2.3 From f08a3c80d61795bb1fc0e576948bca3362ef8e6c Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 31 May 2024 20:03:13 +0300 Subject: Cherry-pick leap.lua changes; other clean up --- indra/newview/scripts/lua/LLDebugSettings.lua | 7 +++---- indra/newview/scripts/lua/UI.lua | 5 ++--- indra/newview/scripts/lua/leap.lua | 12 ++++++++---- indra/newview/scripts/lua/util.lua | 7 ------- 4 files changed, 13 insertions(+), 18 deletions(-) (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/LLDebugSettings.lua b/indra/newview/scripts/lua/LLDebugSettings.lua index 06a8a63727..c250019a00 100644 --- a/indra/newview/scripts/lua/LLDebugSettings.lua +++ b/indra/newview/scripts/lua/LLDebugSettings.lua @@ -1,18 +1,17 @@ leap = require 'leap' -util = require 'util' local LLDebugSettings = {} function LLDebugSettings.set(name, value) - util.check_response(leap.request('LLViewerControl', {op='set', group='Global', key=name, value=value})) + leap.request('LLViewerControl', {op='set', group='Global', key=name, value=value}) end function LLDebugSettings.toggle(name) - util.check_response(leap.request('LLViewerControl', {op='toggle', group='Global', key=name})) + leap.request('LLViewerControl', {op='toggle', group='Global', key=name}) end function LLDebugSettings.get(name) - return util.check_response(leap.request('LLViewerControl', {op='get', group='Global', key=name}))['value'] + return leap.request('LLViewerControl', {op='get', group='Global', key=name})['value'] end return LLDebugSettings diff --git a/indra/newview/scripts/lua/UI.lua b/indra/newview/scripts/lua/UI.lua index 6101c7a312..24f822bbd9 100644 --- a/indra/newview/scripts/lua/UI.lua +++ b/indra/newview/scripts/lua/UI.lua @@ -1,17 +1,16 @@ -- Engage the UI LLEventAPI leap = require 'leap' -util = require 'util' local UI = {} function UI.call(func, parameter) -- 'call' is fire-and-forget - util.check_response(leap.request('UI', {op='call', ['function']=func, parameter=parameter})) + leap.request('UI', {op='call', ['function']=func, parameter=parameter}) end function UI.getValue(path) - return util.check_response(leap.request('UI', {op='getValue', path=path}))['value'] + return leap.request('UI', {op='getValue', path=path})['value'] end return UI diff --git a/indra/newview/scripts/lua/leap.lua b/indra/newview/scripts/lua/leap.lua index ade91789f0..cfb7377523 100644 --- a/indra/newview/scripts/lua/leap.lua +++ b/indra/newview/scripts/lua/leap.lua @@ -161,10 +161,12 @@ function leap.request(pump, data) dbg('leap.request(%s, %s) got %s: %s', pump, data, ok, response) -- kill off temporary WaitForReqid object, even if error pending[reqid] = nil - if ok then - return response - else + if not ok then error(response) + elseif response.error then + error(response.error) + else + return response end end @@ -186,7 +188,7 @@ function leap.generate(pump, data, checklast) local ok, response, resumed_with repeat ok, response = pcall(waitfor.wait, waitfor) - if not ok then + if (not ok) or response.error then break end -- can resume(false) to terminate generate() and clean up @@ -196,6 +198,8 @@ function leap.generate(pump, data, checklast) pending[reqid] = nil if not ok then error(response) + elseif response.error then + error(response.error) end end diff --git a/indra/newview/scripts/lua/util.lua b/indra/newview/scripts/lua/util.lua index 404efdf09e..a2191288f6 100644 --- a/indra/newview/scripts/lua/util.lua +++ b/indra/newview/scripts/lua/util.lua @@ -41,11 +41,4 @@ function util.equal(t1, t2) return util.empty(temp) end -function util.check_response(res) - if res.error then - error(res.error) - end - return res -end - return util -- cgit v1.2.3 From 894dd1937511df08fa57c5e586d40a7778473dae Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 31 May 2024 17:02:25 -0400 Subject: Tweak for current Lua dbg() convention. --- indra/newview/scripts/lua/ErrorQueue.lua | 2 +- indra/newview/scripts/lua/WaitQueue.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/ErrorQueue.lua b/indra/newview/scripts/lua/ErrorQueue.lua index 6ed1c10d5c..13e4e92941 100644 --- a/indra/newview/scripts/lua/ErrorQueue.lua +++ b/indra/newview/scripts/lua/ErrorQueue.lua @@ -3,8 +3,8 @@ -- raise that error. local WaitQueue = require('WaitQueue') --- local dbg = require('printf') local function dbg(...) end +-- local dbg = require('printf') local ErrorQueue = WaitQueue:new() diff --git a/indra/newview/scripts/lua/WaitQueue.lua b/indra/newview/scripts/lua/WaitQueue.lua index ad4fdecf43..6bcb9d62c2 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 dbg = require('printf') local function dbg(...) end +-- local dbg = require('printf') local WaitQueue = Queue:new() -- cgit v1.2.3 From de719553fddc381e274d8bff218ab4e3f6691945 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 31 May 2024 17:15:06 -0400 Subject: Add timers.lua API module and test_timers.lua test program. Since timers presents a timers.Timer Lua class supporting queries and cancellation, make TimersListener::scheduleAfter() and scheduleEvery() respond immediately so the newly constructed Timer object has the reqid necessary to perform those subsequent operations. This requires that Lua invocations of these operations avoid calling the caller's callback with that initial response. Reinvent leap.generate() to return a Lua object supporting next() and done() methods. A plain Lua coroutine that (indirectly) calls fiber.wait() confuses the fiber scheduler, so avoid implementing generate() as a Lua coroutine. Add a bit more leap.lua diagnostic output. --- indra/newview/scripts/lua/leap.lua | 59 ++++++++++------- indra/newview/scripts/lua/test_timers.lua | 39 ++++++++++++ indra/newview/scripts/lua/timers.lua | 101 ++++++++++++++++++++++++++++++ 3 files changed, 177 insertions(+), 22 deletions(-) create mode 100644 indra/newview/scripts/lua/test_timers.lua create mode 100644 indra/newview/scripts/lua/timers.lua (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/leap.lua b/indra/newview/scripts/lua/leap.lua index 69cedb4c9e..8caae24e94 100644 --- a/indra/newview/scripts/lua/leap.lua +++ b/indra/newview/scripts/lua/leap.lua @@ -172,36 +172,45 @@ function leap.request(pump, data) end -- Send the specified request LLSD, expecting an arbitrary number of replies. --- Each one is yielded on receipt. If you omit checklast, this is an infinite --- generator; it's up to the caller to recognize when the last reply has been --- received, and stop resuming for more. --- --- If you pass checklast=, each response event is --- passed to that callable (after the yield). When the callable returns --- true, the generator terminates in the usual way. +-- Each one is returned on request. +-- +-- Usage: +-- sequence = leap.generate(pump, data) +-- repeat +-- response = sequence.next() +-- until last(response) +-- (last() means whatever test the caller wants to perform on response) +-- sequence.done() -- -- See request() remarks about ["reqid"]. +-- +-- Note: this seems like a prime use case for Lua coroutines. But in a script +-- using fibers.lua, a "wild" coroutine confuses the fiber scheduler. If +-- generate() were itself a coroutine, it would call WaitForReqid:wait(), +-- which would yield -- thereby resuming generate() WITHOUT waiting. 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, waitfor = requestSetup(pump, data) - local ok, response, resumed_with - repeat - ok, response = pcall(waitfor.wait, waitfor) - if (not ok) or response.error then - break + return { + next = function() + dbg('leap.generate(%s).next() about to wait on %s', reqid, tostring(waitfor)) + local ok, response = pcall(waitfor.wait, waitfor) + dbg('leap.generate(%s).next() got %s: %s', reqid, ok, response) + if not ok then + error(response) + elseif response.error then + error(response.error) + else + return response + end + end, + done = function() + -- cleanup consists of removing our WaitForReqid from pending + pending[reqid] = nil end - -- can resume(false) to terminate generate() and clean up - resumed_with = coroutine.yield(response) - until (checklast and checklast(response)) or (resumed_with == false) - -- If we break the above loop, whether or not due to error, clean up. - pending[reqid] = nil - if not ok then - error(response) - elseif response.error then - error(response.error) - end + } end -- Send the specified request LLSD, expecting an immediate reply followed by @@ -220,7 +229,9 @@ function leap.eventstream(pump, data, callback) end -- No error, so far so good: -- call the callback with the first response just in case + dbg('leap.eventstream(%s): first callback', reqid) local ok, done = pcall(callback, response) + dbg('leap.eventstream(%s) got %s, %s', reqid, ok, done) if not ok then -- clean up our WaitForReqid waitfor:close() @@ -236,13 +247,17 @@ function leap.eventstream(pump, data, callback) pump, function () local ok, done + local nth = 1 repeat event = waitfor:wait() if not event then -- wait() returns nil once the queue is closed (e.g. cancelreq()) ok, done = true, true else + nth += 1 + dbg('leap.eventstream(%s): callback %d', reqid, nth) ok, done = pcall(callback, event) + dbg('leap.eventstream(%s) got %s, %s', reqid, ok, done) end -- not ok means callback threw an error (caught as 'done') -- done means callback succeeded but wants to stop diff --git a/indra/newview/scripts/lua/test_timers.lua b/indra/newview/scripts/lua/test_timers.lua new file mode 100644 index 0000000000..53a2dc83f2 --- /dev/null +++ b/indra/newview/scripts/lua/test_timers.lua @@ -0,0 +1,39 @@ +local timers = require 'timers' + +print('t0:new(10)') +start = os.clock() +t0 = timers.Timer:new(10, function() print('t0 fired at', os.clock() - start) end) +print('t0:isRunning(): ', t0:isRunning()) +print('t0:timeUntilCall(): ', t0:timeUntilCall()) +print('t0:cancel(): ', t0:cancel()) +print('t0:isRunning(): ', t0:isRunning()) +print('t0:timeUntilCall(): ', t0:timeUntilCall()) +print('t0:cancel(): ', t0:cancel()) + +print('t1:new(5)') +start = os.clock() +t1 = timers.Timer:new(5, function() print('t1 fired at', os.clock() - start) end) + +print('t2:new(2)') +start = os.clock() +t2 = timers.Timer:new(2) +function t2:tick() + print('t2 fired at', os.clock() - start) +end + +start = os.clock() +timers.Timer:new(5, 'wait') +print(string.format('Timer(5) waited %f seconds', os.clock() - start)) + +start = os.clock() +timers.Timer:new( + 2, + coroutine.wrap(function() + for i = 1,5 do + print('repeat(2) timer fired at ', os.clock() - start) + coroutine.yield(nil) -- keep running + end + print('repeat(2) timer fired last at ', os.clock() - start) + return true -- stop + end), + true) -- iterate diff --git a/indra/newview/scripts/lua/timers.lua b/indra/newview/scripts/lua/timers.lua new file mode 100644 index 0000000000..e0d27a680d --- /dev/null +++ b/indra/newview/scripts/lua/timers.lua @@ -0,0 +1,101 @@ +-- Access to the viewer's time-delay facilities + +local leap = require 'leap' + +local timers = {} + +local function dbg(...) end +-- local dbg = require 'printf' + +timers.Timer = {} + +-- delay: time in seconds until callback +-- callback: 'wait', or function to call when timer fires (self:tick if nil) +-- iterate: if non-nil, call callback repeatedly until it returns non-nil +-- (ignored if 'wait') +function timers.Timer:new(delay, callback, iterate) + local obj = setmetatable({}, self) + self.__index = self + + if callback == 'wait' then + dbg('scheduleAfter(%d):', delay) + sequence = leap.generate('Timers', {op='scheduleAfter', after=delay}) + -- ignore the immediate return + dbg('scheduleAfter(%d) -> %s', delay, + sequence.next()) + -- this call is where we wait for real + dbg('next():') + dbg('next() -> %s', + sequence.next()) + sequence.done() + return + end + + callback = callback or function() obj:tick() end + + local first = true + if iterate then + obj.id = leap.eventstream( + 'Timers', + {op='scheduleEvery', every=delay}, + function (event) + local reqid = event.reqid + if first then + first = false + dbg('timer(%s) first callback', reqid) + -- discard the first (immediate) response: don't call callback + return nil + else + dbg('timer(%s) nth callback', reqid) + return callback(event) + end + end + ).reqid + else + obj.id = leap.eventstream( + 'Timers', + {op='scheduleAfter', after=delay}, + function (event) + -- Arrange to return nil the first time, true the second. This + -- callback is called immediately with the response to + -- 'scheduleAfter', and if we immediately returned true, we'd + -- be done, and the subsequent timer event would be discarded. + if first then + first = false + -- Caller doesn't expect an immediate callback. + return nil + else + callback(event) + -- Since caller doesn't want to iterate, the value + -- returned by the callback is irrelevant: just stop after + -- this one and only call. + return true + end + end + ).reqid + end + + return obj +end + +function timers.Timer:tick() + error('Pass a callback to Timer:new(), or override Timer:tick()') +end + +function timers.Timer:cancel() + local ok = leap.request('Timers', {op='cancel', id=self.id}).ok + leap.cancelreq(self.id) + return ok +end + +function timers.Timer:isRunning() + return leap.request('Timers', {op='isRunning', id=self.id}).running +end + +-- returns (true, seconds left) for a live timer, else (false, 0) +function timers.Timer:timeUntilCall() + local result = leap.request('Timers', {op='timeUntilCall', id=self.id}) + return result.ok, result.remaining +end + +return timers -- cgit v1.2.3 From 47821cf53f89acec4ed6d465e14d7793d2420b41 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 3 Jun 2024 11:48:11 -0400 Subject: Leverage new leap.eventstream() function in Floater.lua. --- indra/newview/scripts/lua/Floater.lua | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/Floater.lua b/indra/newview/scripts/lua/Floater.lua index 76efd47c43..75696533e4 100644 --- a/indra/newview/scripts/lua/Floater.lua +++ b/indra/newview/scripts/lua/Floater.lua @@ -46,10 +46,18 @@ function Floater:new(path, extra) end function Floater:show() - local event = leap.request('LLFloaterReg', self._command) + -- leap.eventstream() returns the first response, and launches a + -- background fiber to call the passed callback with all subsequent + -- responses. + local event = leap.eventstream( + 'LLFloaterReg', + self._command, + -- handleEvents() returns false when done. + -- eventstream() expects a true return when done. + function(event) return not self:handleEvents(event) end) self._pump = event.command_name - -- we use the returned reqid to claim subsequent unsolicited events - local reqid = event.reqid + -- we might need the returned reqid to cancel the eventstream() fiber + self.reqid = event.reqid -- The response to 'showLuaFloater' *is* the 'post_build' event. Check if -- subclass has a post_build() method. Honor the convention that if @@ -57,22 +65,6 @@ function Floater:show() if not self:handleEvents(event) then return end - - local waitfor = leap.WaitFor:new(-1, self.name) - function waitfor:filter(pump, data) - if data.reqid == reqid then - return data - end - end - - fiber.launch( - self.name, - function () - event = waitfor:wait() - while event and self:handleEvents(event) do - event = waitfor:wait() - end - end) end function Floater:post(action) @@ -125,7 +117,7 @@ function Floater:handleEvents(event_data) -- We check for event() method before recognizing floater_close in case -- the consumer needs to react specially to closing the floater. Now that -- we've checked, recognize it ourselves. Returning false terminates the - -- anonymous fiber function launched by show(). + -- anonymous fiber function launched by leap.eventstream(). if event == _event('floater_close') then LL.print_warning(self.name .. ' closed') return false -- cgit v1.2.3 From 9e6cf32add0a857b4e28c638bd378a8d3f70fcdb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 4 Jun 2024 09:52:55 -0400 Subject: Comment the intent of test_timers.lua so the user need not reverse-engineer the code to figure out the output. --- indra/newview/scripts/lua/test_timers.lua | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'indra/newview/scripts/lua') diff --git a/indra/newview/scripts/lua/test_timers.lua b/indra/newview/scripts/lua/test_timers.lua index 53a2dc83f2..ed0de070f7 100644 --- a/indra/newview/scripts/lua/test_timers.lua +++ b/indra/newview/scripts/lua/test_timers.lua @@ -1,5 +1,11 @@ local timers = require 'timers' +-- This t0 is constructed for 10 seconds, but its purpose is to exercise the +-- query and cancel methods. It would print "t0 fired at..." if it fired, but +-- it doesn't, so you don't see that message. Instead you see that isRunning() +-- is true, that timeUntilCall() is (true, close to 10), that cancel() returns +-- true. After that, isRunning() is false, timeUntilCall() returns (false, 0), +-- and a second cancel() returns false. print('t0:new(10)') start = os.clock() t0 = timers.Timer:new(10, function() print('t0 fired at', os.clock() - start) end) @@ -10,10 +16,15 @@ print('t0:isRunning(): ', t0:isRunning()) print('t0:timeUntilCall(): ', t0:timeUntilCall()) print('t0:cancel(): ', t0:cancel()) +-- t1 is supposed to fire after 5 seconds, but it doesn't wait, so you see the +-- t2 messages immediately after. print('t1:new(5)') start = os.clock() t1 = timers.Timer:new(5, function() print('t1 fired at', os.clock() - start) end) +-- t2 illustrates that instead of passing a callback to new(), you can +-- override the timer instance's tick() method. But t2 doesn't wait either, so +-- you see the Timer(5) message immediately. print('t2:new(2)') start = os.clock() t2 = timers.Timer:new(2) @@ -21,10 +32,23 @@ function t2:tick() print('t2 fired at', os.clock() - start) end +-- This anonymous timer blocks the calling fiber for 5 seconds. Other fibers +-- are free to run during that time, so you see the t2 callback message and +-- then the t1 callback message before the Timer(5) completion message. +print('Timer(5) waiting') start = os.clock() timers.Timer:new(5, 'wait') print(string.format('Timer(5) waited %f seconds', os.clock() - start)) +-- This test demonstrates a repeating timer. It also shows that you can (but +-- need not) use a coroutine as the timer's callback function: unlike Python, +-- Lua doesn't disinguish between yield() and return. A coroutine wrapped with +-- coroutine.wrap() looks to Lua just like any other function that you can +-- call repeatedly and get a result each time. We use that to count the +-- callback calls and stop after a certain number. Of course that could also +-- be arranged in a plain function by incrementing a script-scope counter, but +-- it's worth knowing that a coroutine timer callback can be used to manage +-- more complex control flows. start = os.clock() timers.Timer:new( 2, -- cgit v1.2.3