From 14c8fc3768d978205bf17ffc1905c2772afbd434 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 28 Aug 2024 16:47:38 -0400 Subject: Add `LL.setdtor()` function to add a "destructor" to any Lua object. `setdtor('description', object, function)` returns a proxy userdata object referencing object and function. When the proxy is garbage-collected, or at the end of the script, its destructor calls `function(object)`. The original object may be retrieved as `proxy._target`, e.g. to pass it to the `table` library. The proxy also has a metatable with metamethods supporting arithmetic operations, string concatenation, length and table indexing. For other operations, retrieve `proxy._target`. (But don't assign to `proxy._target`. It will appear to work, in that subsequent references to `proxy._target` will retrieve the replacement object -- however, the destructor will still call `function(original object)`.) Fix bugs in `lua_setfieldv()`, `lua_rawgetfield()` and `lua_rawsetfield()`. Add C++ functions `lua_destroyuserdata()` to explicitly destroy a `lua_emplace()` userdata object, plus `lua_destroybounduserdata()`. The latter can bind such a userdata object as an upvalue to pass to `LL.atexit()`. Make `LL.help()` and `LL.leaphelp()` help text include the `LL.` prefix. --- indra/newview/scripts/lua/test_setdtor.lua | 62 ++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 indra/newview/scripts/lua/test_setdtor.lua (limited to 'indra/newview/scripts') diff --git a/indra/newview/scripts/lua/test_setdtor.lua b/indra/newview/scripts/lua/test_setdtor.lua new file mode 100644 index 0000000000..743c5168d0 --- /dev/null +++ b/indra/newview/scripts/lua/test_setdtor.lua @@ -0,0 +1,62 @@ +inspect = require 'inspect' + +print('initial setdtor') +bye = LL.setdtor('initial setdtor', 'Goodbye world!', print) + +print('arithmetic') +n = LL.setdtor('arithmetic', 11, print) +print("n =", n) +print("n._target =", n._target) +print("getmetatable(n) =", inspect(getmetatable(n))) +print("-n =", -n) +for i = 10, 12 do + -- Comparison metamethods are only called if both operands have the same + -- metamethod. + tempi = LL.setdtor('tempi', i, function(n) print('temp', i) end) + print(`n < {i}`, n < tempi) + print(`n <= {i}`, n <= tempi) + print(`n == {i}`, n == tempi) + print(`n ~= {i}`, n ~= tempi) + print(`n >= {i}`, n >= tempi) + print(`n > {i}`, n > tempi) +end +for i = 2, 3 do + print(`n + {i} =`, n + i) + print(`{i} + n =`, i + n) + print(`n - {i} =`, n - i) + print(`{i} - n =`, i - n) + print(`n * {i} =`, n * i) + print(`{i} * n =`, i * n) + print(`n / {i} =`, n / i) + print(`{i} / n =`, i / n) + print(`n % {i} =`, n % i) + print(`{i} % n =`, i % n) + print(`n ^ {i} =`, n ^ i) + print(`{i} ^ n =`, i ^ n) +end + +print('string') +s = LL.setdtor('string', 'hello', print) +print('s =', s) +print('#s =', #s) +print('s .. " world" =', s .. " world") +print('"world " .. s =', "world " .. s) + +print('table') +t = LL.setdtor('table', {'[1]', '[2]', abc='.abc', def='.def'}, + function(t) print(inspect(t)) end) +print('t =', inspect(t)) +print('t._target =', inspect(t._target)) +print('#t =', #t) +print('t[2] =', t[2]) +print('t.def =', t.def) +t[1] = 'new [1]' +print('t[1] =', t[1]) + +print('function') +f = LL.setdtor('function', function(a, b) return (a .. b) end, print) +print('f =', f) +print('f._target =', f._target) +print('f("Hello", " world") =', f("Hello", " world")) + +print('cleanup') -- cgit v1.2.3 From 364ea79ab3a4d48e0d10fbeabb9b8e88f226baac Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 28 Aug 2024 19:34:05 -0400 Subject: Prevent erroneous assignment to LL.setdtor() proxy._target field. Trim redundant output from test_setdtor.lua. --- indra/newview/scripts/lua/test_setdtor.lua | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'indra/newview/scripts') diff --git a/indra/newview/scripts/lua/test_setdtor.lua b/indra/newview/scripts/lua/test_setdtor.lua index 743c5168d0..61ed86dcc8 100644 --- a/indra/newview/scripts/lua/test_setdtor.lua +++ b/indra/newview/scripts/lua/test_setdtor.lua @@ -7,6 +7,7 @@ print('arithmetic') n = LL.setdtor('arithmetic', 11, print) print("n =", n) print("n._target =", n._target) +print(pcall(function() n._target = 12 end)) print("getmetatable(n) =", inspect(getmetatable(n))) print("-n =", -n) for i = 10, 12 do @@ -20,20 +21,19 @@ for i = 10, 12 do print(`n >= {i}`, n >= tempi) print(`n > {i}`, n > tempi) end -for i = 2, 3 do - print(`n + {i} =`, n + i) - print(`{i} + n =`, i + n) - print(`n - {i} =`, n - i) - print(`{i} - n =`, i - n) - print(`n * {i} =`, n * i) - print(`{i} * n =`, i * n) - print(`n / {i} =`, n / i) - print(`{i} / n =`, i / n) - print(`n % {i} =`, n % i) - print(`{i} % n =`, i % n) - print(`n ^ {i} =`, n ^ i) - print(`{i} ^ n =`, i ^ n) -end +i = 2 +print(`n + {i} =`, n + i) +print(`{i} + n =`, i + n) +print(`n - {i} =`, n - i) +print(`{i} - n =`, i - n) +print(`n * {i} =`, n * i) +print(`{i} * n =`, i * n) +print(`n / {i} =`, n / i) +print(`{i} / n =`, i / n) +print(`n % {i} =`, n % i) +print(`{i} % n =`, i % n) +print(`n ^ {i} =`, n ^ i) +print(`{i} ^ n =`, i ^ n) print('string') s = LL.setdtor('string', 'hello', print) -- cgit v1.2.3 From 2d5cf36be6e0e367efec2bfa01378146269f33db Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 29 Aug 2024 21:36:39 -0400 Subject: Support next(), pairs(), ipairs() for LL.setdtor() table proxies. Replace the global next(), pairs() and ipairs() functions with a C++ function that drills down through layers of setdtor() proxy objects and then forwards the updated arguments to the original global function. Add a Luau __iter() metamethod to setdtor() proxy objects that, like other proxy metamethods, drills down to the underlying _target object. __iter() recognizes the case of a _target table which itself has a __iter() metamethod. Also add __idiv() metamethod to support integer division. Add tests for proxy // division, next(proxy), next(proxy, key), pairs(proxy), ipairs(proxy) and 'for k, v in proxy'. Also test the case where the table wrapped in the proxy has an __iter() metamethod of its own. --- indra/newview/scripts/lua/test_setdtor.lua | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'indra/newview/scripts') diff --git a/indra/newview/scripts/lua/test_setdtor.lua b/indra/newview/scripts/lua/test_setdtor.lua index 61ed86dcc8..ec5cd47e93 100644 --- a/indra/newview/scripts/lua/test_setdtor.lua +++ b/indra/newview/scripts/lua/test_setdtor.lua @@ -30,6 +30,8 @@ print(`n * {i} =`, n * i) print(`{i} * n =`, i * n) print(`n / {i} =`, n / i) print(`{i} / n =`, i / n) +print(`n // {i} =`, n // i) +print(`{i} // n =`, i // n) print(`n % {i} =`, n % i) print(`{i} % n =`, i % n) print(`n ^ {i} =`, n ^ i) @@ -48,10 +50,37 @@ t = LL.setdtor('table', {'[1]', '[2]', abc='.abc', def='.def'}, print('t =', inspect(t)) print('t._target =', inspect(t._target)) print('#t =', #t) +print('next(t) =', next(t)) +print('next(t, 1) =', next(t, 1)) print('t[2] =', t[2]) print('t.def =', t.def) t[1] = 'new [1]' print('t[1] =', t[1]) +print('for k, v in pairs(t) do') +for k, v in pairs(t) do + print(`{k}: {v}`) +end +print('for k, v in ipairs(t) do') +for k, v in ipairs(t) do + print(`{k}: {v}`) +end +print('for k, v in t do') +for k, v in t do + print(`{k}: {v}`) +end +-- and now for something completely different +setmetatable( + t._target, + { + __iter = function(arg) + return next, {'alternate', '__iter'} + end + } +) +print('for k, v in t with __iter() metamethod do') +for k, v in t do + print(`{k}: {v}`) +end print('function') f = LL.setdtor('function', function(a, b) return (a .. b) end, print) -- cgit v1.2.3 From 23f0aafb74551a741de8c87d62d74e7c6cee8b01 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 31 Aug 2024 09:42:52 -0400 Subject: Give certain LLInventory queries an API based on result sets. Introduce abstract base class InvResultSet, derived from LLIntTracker so each instance has a unique int key. InvResultSet supports virtual getLength() and getSlice() operations. getSlice() returns an LLSD array limited to MAX_ITEM_LIMIT result set entries. It permits retrieving a "slice" of the contained result set starting at an arbitrary index. A sequence of getSlice() calls can eventually retrieve a whole result set. InvResultSet has subclasses CatResultSet containing cat_array_t, and ItemResultSet containing item_array_t. Each implements a virtual method that produces an LLSD map from a single array item. Make LLInventoryListener::getItemsInfo(), getDirectDescendants() and collectDescendantsIf() instantiate heap CatResultSet and ItemResultSet objects containing the resultant LLPointer arrays, and return their int keys for categories and items. Add LLInventoryListener::getSlice() and closeResult() methods that accept the int keys of result sets. getSlice() returns the requested LLSD array to its caller, while closeResult() is fire-and-forget. Because bulk data transfer is now performed by getSlice() rather than by collectDescendantsIf(), change the latter's "limit" default to unlimited. Allow the C++ code to collect an arbitrary number of LLPointer array entries, as long as getSlice() limits retrieval overhead. Spell "descendants" correctly, unlike the "descendents" spelling embedded in the rest of the viewer... sigh. Make the Lua module provide both spellings. Make MAX_ITEM_LIMIT a U32 instead of F32. In LLInventory.lua, store int result set keys from 'getItemsInfo', 'getDirectDescendants' and 'collectDescendantsIf' in a table with a close() function. The close() function invokes 'closeResult' with the bound int keys. Give that table an __index() metamethod that recognizes only 'categories' and 'items' keys: anything else returns nil. For either of the recognized keys, call 'getSlice' with the corresponding result set key to retrieve (the initial slice of) the actual result set. Cache that result. Lazy retrieval means that if the caller only cares about categories, or only about items, the other result set need never be retrieved at all. This is a first step: like the previous code, it still retrieves only up to the first 100 result set entries. But the C++ code now supports retrieval of additional slices, so extending result set retrieval is mostly Lua work. Finally, wrap the table-with-metamethod in an LL.setdtor() proxy whose destructor calls its close() method to tell LLInventoryListener to destroy the CatResultSet and ItemResultSet with the bound keys. --- indra/newview/scripts/lua/require/LLInventory.lua | 79 +++++++++++++++++++---- 1 file changed, 68 insertions(+), 11 deletions(-) (limited to 'indra/newview/scripts') diff --git a/indra/newview/scripts/lua/require/LLInventory.lua b/indra/newview/scripts/lua/require/LLInventory.lua index dd1b910250..0ff6b9fb37 100644 --- a/indra/newview/scripts/lua/require/LLInventory.lua +++ b/indra/newview/scripts/lua/require/LLInventory.lua @@ -1,12 +1,66 @@ local leap = require 'leap' local mapargs = require 'mapargs' +local function result(keys) + return LL.setdtor( + 'LLInventory result', + setmetatable( + -- the basic table wrapped by setmetatable just captures the int + -- result-set keys from 'keys', but with underscore prefixes + { + _categories=keys.categories, + _items=keys.items, + -- call result:close() to release result sets before garbage + -- collection or script completion + close = function(self) + leap.send('LLInventory', + {op='closeResult', + result={self._categories, self._items}}) + end + }, + -- The caller of one of our methods that returns a result set + -- isn't necessarily interested in both categories and items, so + -- don't proactively populate both. Instead, when caller references + -- either 'categories' or 'items', the __index() metamethod + -- populates that field. + { + __index = function(t, key) + -- we really don't care about references to any other field + if not table.find({'categories', 'items'}, key) then + return nil + end + -- We cleverly saved the int result set key in a field + -- with the same name but an underscore prefix. + local resultkey = t['_' .. key] + -- TODO: This only ever fetches the FIRST slice. What we + -- really want is to return a table with metamethods that + -- manage indexed access and table iteration. + -- Remember our C++ entry point uses 0-relative indexing. + local slice = leap.request( + 'LLInventory', + {op='getSlice', result=resultkey, index=0}).slice + print(`getSlice({resultkey}, 0) => {slice} ({#slice} entries)`) + -- cache this slice for future reference + t[key] = slice + return slice + end + } + ), + -- When the table-with-metatable above is destroyed, tell LLInventory + -- we're done with its result sets -- whether or not we ever fetched + -- either of them. + function(keys) + keys:close() + end + ) +end + local LLInventory = {} -- Get the items/folders info by provided IDs, -- reply will contain "items" and "categories" tables accordingly function LLInventory.getItemsInfo(item_ids) - return leap.request('LLInventory', {op = 'getItemsInfo', item_ids=item_ids}) + return result(leap.request('LLInventory', {op = 'getItemsInfo', item_ids=item_ids})) end -- Get the table of folder type names, which can be later used to get the ID of the basic folders @@ -19,30 +73,33 @@ function LLInventory.getBasicFolderID(ft_name) return leap.request('LLInventory', {op = 'getBasicFolderID', ft_name=ft_name}).id end --- Get the table of asset type names, which can be later used to get the specific items via LLInventory.collectDescendentsIf(...) +-- Get the table of asset type names, which can be later used to get the specific items via LLInventory.collectDescendantsIf(...) function LLInventory.getAssetTypeNames() return leap.request('LLInventory', {op = 'getAssetTypeNames'}).names end --- Get the direct descendents of the 'folder_id' provided, +-- Get the direct descendants of the 'folder_id' provided, -- reply will contain "items" and "categories" tables accordingly -function LLInventory.getDirectDescendents(folder_id) - return leap.request('LLInventory', {op = 'getDirectDescendents', folder_id=folder_id}) +function LLInventory.getDirectDescendants(folder_id) + return result(leap.request('LLInventory', {op = 'getDirectDescendants', folder_id=folder_id})) end +-- backwards compatibility +LLInventory.getDirectDescendents = LLInventory.getDirectDescendants --- Get the descendents of the 'folder_id' provided, which pass specified filters +-- Get the descendants of the 'folder_id' provided, which pass specified filters -- reply will contain "items" and "categories" tables accordingly --- LLInventory.collectDescendentsIf{ folder_id -- parent folder ID +-- LLInventory.collectDescendantsIf{ folder_id -- parent folder ID -- [, name] -- name (substring) -- [, desc] -- description (substring) -- [, type] -- asset type -- [, limit] -- item count limit in reply, maximum and default is 100 -- [, filter_links]} -- EXCLUDE_LINKS - don't show links, ONLY_LINKS - only show links, INCLUDE_LINKS - show links too (default) -function LLInventory.collectDescendentsIf(...) +function LLInventory.collectDescendantsIf(...) local args = mapargs('folder_id,name,desc,type,filter_links,limit', ...) - args.op = 'collectDescendentsIf' - return leap.request('LLInventory', args) + args.op = 'collectDescendantsIf' + return result(leap.request('LLInventory', args)) end - +-- backwards compatibility +LLInventory.collectDescendentsIf = LLInventory.collectDescendantsIf return LLInventory -- cgit v1.2.3 From 1101ed699a0c3c23c0bb11267d390febfdc02409 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 2 Sep 2024 16:41:09 -0400 Subject: Introduce result_view.lua, and use it in LLInventory.lua. result_view(key_length, fetch) returns a virtual view of a potentially-large C++ result set. Given the result-set key, its total length and a function fetch(key, start) => (slice, adjusted start), the read-only table returned by result_view() manages indexed access and table iteration over the entire result set, fetching a slice at a time as required. Change LLInventory to use result_view() instead of only ever fetching the first slice of a result set. TODO: This depends on the viewer's "LLInventory" listener returning the total result set length as well as the result set key. It does not yet return the length. --- indra/newview/scripts/lua/require/LLInventory.lua | 38 +++++++------- indra/newview/scripts/lua/require/result_view.lua | 62 +++++++++++++++++++++++ 2 files changed, 82 insertions(+), 18 deletions(-) create mode 100644 indra/newview/scripts/lua/require/result_view.lua (limited to 'indra/newview/scripts') diff --git a/indra/newview/scripts/lua/require/LLInventory.lua b/indra/newview/scripts/lua/require/LLInventory.lua index 0ff6b9fb37..ce501e75f3 100644 --- a/indra/newview/scripts/lua/require/LLInventory.lua +++ b/indra/newview/scripts/lua/require/LLInventory.lua @@ -1,12 +1,14 @@ local leap = require 'leap' local mapargs = require 'mapargs' +local result_view = require 'result_view' local function result(keys) return LL.setdtor( 'LLInventory result', setmetatable( -- the basic table wrapped by setmetatable just captures the int - -- result-set keys from 'keys', but with underscore prefixes + -- result-set {key, length} pairs from 'keys', but with underscore + -- prefixes { _categories=keys.categories, _items=keys.items, @@ -15,7 +17,7 @@ local function result(keys) close = function(self) leap.send('LLInventory', {op='closeResult', - result={self._categories, self._items}}) + result={self._categories[1], self._items[1]}}) end }, -- The caller of one of our methods that returns a result set @@ -24,25 +26,25 @@ local function result(keys) -- either 'categories' or 'items', the __index() metamethod -- populates that field. { - __index = function(t, key) + __index = function(t, field) -- we really don't care about references to any other field - if not table.find({'categories', 'items'}, key) then + if not table.find({'categories', 'items'}, field) then return nil end - -- We cleverly saved the int result set key in a field - -- with the same name but an underscore prefix. - local resultkey = t['_' .. key] - -- TODO: This only ever fetches the FIRST slice. What we - -- really want is to return a table with metamethods that - -- manage indexed access and table iteration. - -- Remember our C++ entry point uses 0-relative indexing. - local slice = leap.request( - 'LLInventory', - {op='getSlice', result=resultkey, index=0}).slice - print(`getSlice({resultkey}, 0) => {slice} ({#slice} entries)`) - -- cache this slice for future reference - t[key] = slice - return slice + local view = result_view( + -- We cleverly saved the result set {key, length} pair in + -- a field with the same name but an underscore prefix. + t['_' .. field], + function(key, start) + local fetched = leap.request( + 'LLInventory', + {op='getSlice', result=key, index=start}) + return fetched.slice, fetched.start + end + ) + -- cache that view for future reference + t[field] = view + return view end } ), diff --git a/indra/newview/scripts/lua/require/result_view.lua b/indra/newview/scripts/lua/require/result_view.lua new file mode 100644 index 0000000000..4a58636f2f --- /dev/null +++ b/indra/newview/scripts/lua/require/result_view.lua @@ -0,0 +1,62 @@ +-- result_view(key_length, fetch) returns a table which stores only a slice +-- of a result set plus some control values, yet presents read-only virtual +-- access to the entire result set. +-- key_length: {result set key, total result set length} +-- fetch: function(key, start) that returns (slice, adjusted start) +local function result_view(key_length, fetch) + return setmetatable( + { + key=key_length[1], + length=key_length[2], + -- C++ result sets use 0-based indexing, so internally we do too + start=0, + -- start with a dummy array with length 0 + slice={} + }, + { + __len = function(this) + return this.length + end, + __index = function(this, i) + -- right away, convert to 0-relative indexing + i -= 1 + -- can we find this index within the current slice? + local reli = i - this.start + if 0 <= reli and reli < #this.slice then + return this.slice[reli] + end + -- is this index outside the overall result set? + if not (0 <= i and i < this.length) then + return nil + end + -- fetch a new slice starting at i, using provided fetch() + local start + this.slice, start = fetch(key, i) + -- It's possible that caller-provided fetch() function forgot + -- to return the adjusted start index of the new slice. In + -- Lua, 0 tests as true, so if fetch() returned (slice, 0), + -- we'll duly reset this.start to 0. + if start then + this.start = start + end + -- hopefully this slice contains the desired i + return this.slice[i - this.start] + end, + -- We purposely avoid putting any array entries (int keys) into + -- our table so that access to any int key will always call our + -- __index() metamethod. Moreover, we want any table iteration to + -- call __index(table, i) however many times; we do NOT want it to + -- retrieve key, length, start, slice. + -- So turn 'for k, v in result' into 'for k, v in ipairs(result)'. + __iter = ipairs, + -- This result set provides read-only access. + -- We do not support pushing updates to individual items back to + -- C++; for the intended use cases, that makes no sense. + __newindex = function(this, i, value) + error("result_view is a read-only data structure", 2) + end + } + ) +end + +return result_view -- cgit v1.2.3 From 6a4b9b1184c142ca1b317296fa12304bf231fc7d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Sep 2024 12:34:21 -0400 Subject: Add test_result_view.lua; fix minor bugs in result_view.lua. --- indra/newview/scripts/lua/require/result_view.lua | 18 ++++---- indra/newview/scripts/lua/test_result_view.lua | 55 +++++++++++++++++++++++ 2 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 indra/newview/scripts/lua/test_result_view.lua (limited to 'indra/newview/scripts') diff --git a/indra/newview/scripts/lua/require/result_view.lua b/indra/newview/scripts/lua/require/result_view.lua index 4a58636f2f..d53d953c24 100644 --- a/indra/newview/scripts/lua/require/result_view.lua +++ b/indra/newview/scripts/lua/require/result_view.lua @@ -23,7 +23,8 @@ local function result_view(key_length, fetch) -- can we find this index within the current slice? local reli = i - this.start if 0 <= reli and reli < #this.slice then - return this.slice[reli] + -- Lua 1-relative indexing + return this.slice[reli + 1] end -- is this index outside the overall result set? if not (0 <= i and i < this.length) then @@ -31,16 +32,17 @@ local function result_view(key_length, fetch) end -- fetch a new slice starting at i, using provided fetch() local start - this.slice, start = fetch(key, i) + this.slice, start = fetch(this.key, i) -- It's possible that caller-provided fetch() function forgot -- to return the adjusted start index of the new slice. In -- Lua, 0 tests as true, so if fetch() returned (slice, 0), - -- we'll duly reset this.start to 0. - if start then - this.start = start - end - -- hopefully this slice contains the desired i - return this.slice[i - this.start] + -- we'll duly reset this.start to 0. Otherwise, assume the + -- requested index was not adjusted: that the returned slice + -- really does start at i. + this.start = start or i + -- Hopefully this slice contains the desired i. + -- Back to 1-relative indexing. + return this.slice[i - this.start + 1] end, -- We purposely avoid putting any array entries (int keys) into -- our table so that access to any int key will always call our diff --git a/indra/newview/scripts/lua/test_result_view.lua b/indra/newview/scripts/lua/test_result_view.lua new file mode 100644 index 0000000000..304633a472 --- /dev/null +++ b/indra/newview/scripts/lua/test_result_view.lua @@ -0,0 +1,55 @@ +-- Verify the functionality of result_view. +result_view = require 'result_view' + +print('alphabet') +alphabet = "abcdefghijklmnopqrstuvwxyz" +assert(#alphabet == 26) +alphabits = string.split(alphabet, '') + +print('function slice()') +function slice(t, index, count) + return table.move(t, index, index + count - 1, 1, {}) +end + +print('verify slice()') +-- verify that slice() does what we expect +assert(table.concat(slice(alphabits, 4, 3)) == "def") +assert(table.concat(slice(alphabits, 14, 3)) == "nop") +assert(table.concat(slice(alphabits, 25, 3)) == "yz") + +print('function fetch()') +function fetch(key, index) + -- fetch function is defined to be 0-relative: fix for Lua data + -- constrain view of alphabits to slices of at most 3 elements + return slice(alphabits, index+1, 3), index +end + +print('result_view()') +-- for test purposes, key is irrelevant, so just 'key' +view = result_view({'key', #alphabits}, fetch) + +print('function check_iter()') +function check_iter(...) + result = {} + for k, v in ... do + table.insert(result, v) + end + assert(table.concat(result) == alphabet) +end + +print('check_iter(pairs(view))') +check_iter(pairs(view)) +print('check_iter(ipairs(view))') +check_iter(ipairs(view)) +print('check_iter(view)') +check_iter(view) + +print('raw index access') +assert(view[5] == 'e') +assert(view[10] == 'j') +assert(view[15] == 'o') +assert(view[20] == 't') +assert(view[25] == 'y') + +print('Success!') + -- cgit v1.2.3 From 2157fa4fa9acf4db6093b962569274105e4d1fb4 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Sep 2024 12:52:34 -0400 Subject: result_view() now reuses same metatable instance for every table. --- indra/newview/scripts/lua/require/result_view.lua | 97 ++++++++++++----------- 1 file changed, 51 insertions(+), 46 deletions(-) (limited to 'indra/newview/scripts') diff --git a/indra/newview/scripts/lua/require/result_view.lua b/indra/newview/scripts/lua/require/result_view.lua index d53d953c24..c719681c66 100644 --- a/indra/newview/scripts/lua/require/result_view.lua +++ b/indra/newview/scripts/lua/require/result_view.lua @@ -1,3 +1,50 @@ +-- metatable for every result_view() table +local mt = { + __len = function(this) + return this.length + end, + __index = function(this, i) + -- right away, convert to 0-relative indexing + i -= 1 + -- can we find this index within the current slice? + local reli = i - this.start + if 0 <= reli and reli < #this.slice then + -- Lua 1-relative indexing + return this.slice[reli + 1] + end + -- is this index outside the overall result set? + if not (0 <= i and i < this.length) then + return nil + end + -- fetch a new slice starting at i, using provided fetch() + local start + this.slice, start = this.fetch(this.key, i) + -- It's possible that caller-provided fetch() function forgot + -- to return the adjusted start index of the new slice. In + -- Lua, 0 tests as true, so if fetch() returned (slice, 0), + -- we'll duly reset this.start to 0. Otherwise, assume the + -- requested index was not adjusted: that the returned slice + -- really does start at i. + this.start = start or i + -- Hopefully this slice contains the desired i. + -- Back to 1-relative indexing. + return this.slice[i - this.start + 1] + end, + -- We purposely avoid putting any array entries (int keys) into + -- our table so that access to any int key will always call our + -- __index() metamethod. Moreover, we want any table iteration to + -- call __index(table, i) however many times; we do NOT want it to + -- retrieve key, length, start, slice. + -- So turn 'for k, v in result' into 'for k, v in ipairs(result)'. + __iter = ipairs, + -- This result set provides read-only access. + -- We do not support pushing updates to individual items back to + -- C++; for the intended use cases, that makes no sense. + __newindex = function(this, i, value) + error("result_view is a read-only data structure", 2) + end +} + -- result_view(key_length, fetch) returns a table which stores only a slice -- of a result set plus some control values, yet presents read-only virtual -- access to the entire result set. @@ -11,53 +58,11 @@ local function result_view(key_length, fetch) -- C++ result sets use 0-based indexing, so internally we do too start=0, -- start with a dummy array with length 0 - slice={} + slice={}, + fetch=fetch }, - { - __len = function(this) - return this.length - end, - __index = function(this, i) - -- right away, convert to 0-relative indexing - i -= 1 - -- can we find this index within the current slice? - local reli = i - this.start - if 0 <= reli and reli < #this.slice then - -- Lua 1-relative indexing - return this.slice[reli + 1] - end - -- is this index outside the overall result set? - if not (0 <= i and i < this.length) then - return nil - end - -- fetch a new slice starting at i, using provided fetch() - local start - this.slice, start = fetch(this.key, i) - -- It's possible that caller-provided fetch() function forgot - -- to return the adjusted start index of the new slice. In - -- Lua, 0 tests as true, so if fetch() returned (slice, 0), - -- we'll duly reset this.start to 0. Otherwise, assume the - -- requested index was not adjusted: that the returned slice - -- really does start at i. - this.start = start or i - -- Hopefully this slice contains the desired i. - -- Back to 1-relative indexing. - return this.slice[i - this.start + 1] - end, - -- We purposely avoid putting any array entries (int keys) into - -- our table so that access to any int key will always call our - -- __index() metamethod. Moreover, we want any table iteration to - -- call __index(table, i) however many times; we do NOT want it to - -- retrieve key, length, start, slice. - -- So turn 'for k, v in result' into 'for k, v in ipairs(result)'. - __iter = ipairs, - -- This result set provides read-only access. - -- We do not support pushing updates to individual items back to - -- C++; for the intended use cases, that makes no sense. - __newindex = function(this, i, value) - error("result_view is a read-only data structure", 2) - end - } + -- use our special metatable + mt ) end -- cgit v1.2.3 From 93c21b503abdf4f9530064f8c3f1df7ea0dc244f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Sep 2024 13:01:11 -0400 Subject: test_inv_resultset.lua exercises LLInventory's result-set functionality. --- indra/newview/scripts/lua/test_inv_resultset.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 indra/newview/scripts/lua/test_inv_resultset.lua (limited to 'indra/newview/scripts') diff --git a/indra/newview/scripts/lua/test_inv_resultset.lua b/indra/newview/scripts/lua/test_inv_resultset.lua new file mode 100644 index 0000000000..c31cfe3c67 --- /dev/null +++ b/indra/newview/scripts/lua/test_inv_resultset.lua @@ -0,0 +1,18 @@ +local LLInventory = require 'LLInventory' +local inspect = require 'inspect' + +print('basic folders:') +print(inspect(LLInventory.getFolderTypeNames())) + +local folder = LLInventory.getBasicFolderID('my_otfts') +print(`folder = {folder}`) +local result = LLInventory.getDirectDescendants(folder) +print(`type(result) = {type(result)}`) +print(#result.categories, 'categories:') +for i, cat in pairs(result.categories) do + print(`{i}: {cat.name}`) +end +print(#result.items, 'items') +for i, item in pairs(result.items) do + print(`{i}: {item.name}`) +end -- cgit v1.2.3 From 83eace32cfccc672e7a5a2841bd7d844dce0ea3e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Sep 2024 13:13:22 -0400 Subject: Iterate to print landmarks returned by LLInventory. At this point, inspect(landmarks) just returns "". --- indra/newview/scripts/lua/test_LLInventory.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indra/newview/scripts') diff --git a/indra/newview/scripts/lua/test_LLInventory.lua b/indra/newview/scripts/lua/test_LLInventory.lua index 107b0791d4..918ca56a2e 100644 --- a/indra/newview/scripts/lua/test_LLInventory.lua +++ b/indra/newview/scripts/lua/test_LLInventory.lua @@ -5,7 +5,9 @@ LLInventory = require 'LLInventory' my_landmarks_id = LLInventory.getBasicFolderID('landmark') -- Get 3 landmarks from the 'My Landmarks' folder (you can see all folder types via LLInventory.getAssetTypeNames()) landmarks = LLInventory.collectDescendentsIf{folder_id=my_landmarks_id, type="landmark", limit=3} -print(inspect(landmarks)) +for _, landmark in pairs(landmarks.items) do + print(landmark.name) +end -- Get 'Calling Cards' folder id calling_cards_id = LLInventory.getBasicFolderID('callcard') -- cgit v1.2.3 From f3896d37ca625a4f7060ee5139a8825c2f6e6a74 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Sep 2024 20:39:14 -0400 Subject: Generalize Lua-side result-set machinery for other use cases. Change `result_view()` from a simple function to a callable table so we can add conventional/default functions to it: `result_view.fetch()` is a generic `fetch()` function suitable for use with `result_view()`, and `result_view.close()` is a variadic function that closes result sets for whichever keys are passed. This arises from the fact that any `LL::ResultSet` subclass is accessed generically through its base class, therefore we don't need distinct "getSlice" and "closeResult" operations for different `LLEventAPI` listeners. (It might make sense to relocate those operations to a new generic listener, but for now "LLInventory" works.) That lets `result_view()`'s caller omit the `fetch` parameter unless it requires special behavior. Omitting it uses the generic `result_view.fetch()` function. Moreover, every view returned by `result_view()` now contains a close() function that closes that view's result set. The table returned by LLInventory.lua's `result()` function has a `close()` method; that method can now call `result_view.close()` with the two keys of interest. That table's `__index()` metamethod can now leverage `result_view()`'s default `fetch` function. --- indra/newview/scripts/lua/require/LLInventory.lua | 22 ++---- indra/newview/scripts/lua/require/result_view.lua | 83 +++++++++++++++-------- 2 files changed, 62 insertions(+), 43 deletions(-) (limited to 'indra/newview/scripts') diff --git a/indra/newview/scripts/lua/require/LLInventory.lua b/indra/newview/scripts/lua/require/LLInventory.lua index ce501e75f3..9cf72a8678 100644 --- a/indra/newview/scripts/lua/require/LLInventory.lua +++ b/indra/newview/scripts/lua/require/LLInventory.lua @@ -15,9 +15,7 @@ local function result(keys) -- call result:close() to release result sets before garbage -- collection or script completion close = function(self) - leap.send('LLInventory', - {op='closeResult', - result={self._categories[1], self._items[1]}}) + result_view.close(self._categories[1], self._items[1]) end }, -- The caller of one of our methods that returns a result set @@ -31,17 +29,9 @@ local function result(keys) if not table.find({'categories', 'items'}, field) then return nil end - local view = result_view( - -- We cleverly saved the result set {key, length} pair in - -- a field with the same name but an underscore prefix. - t['_' .. field], - function(key, start) - local fetched = leap.request( - 'LLInventory', - {op='getSlice', result=key, index=start}) - return fetched.slice, fetched.start - end - ) + -- We cleverly saved the result set {key, length} pair in + -- a field with the same name but an underscore prefix. + local view = result_view(t['_' .. field]) -- cache that view for future reference t[field] = view return view @@ -51,8 +41,8 @@ local function result(keys) -- When the table-with-metatable above is destroyed, tell LLInventory -- we're done with its result sets -- whether or not we ever fetched -- either of them. - function(keys) - keys:close() + function(res) + res:close() end ) end diff --git a/indra/newview/scripts/lua/require/result_view.lua b/indra/newview/scripts/lua/require/result_view.lua index c719681c66..5301d7838c 100644 --- a/indra/newview/scripts/lua/require/result_view.lua +++ b/indra/newview/scripts/lua/require/result_view.lua @@ -1,34 +1,36 @@ +local leap = require 'leap' + -- metatable for every result_view() table local mt = { - __len = function(this) - return this.length + __len = function(self) + return self.length end, - __index = function(this, i) + __index = function(self, i) -- right away, convert to 0-relative indexing i -= 1 -- can we find this index within the current slice? - local reli = i - this.start - if 0 <= reli and reli < #this.slice then + local reli = i - self.start + if 0 <= reli and reli < #self.slice then -- Lua 1-relative indexing - return this.slice[reli + 1] + return self.slice[reli + 1] end -- is this index outside the overall result set? - if not (0 <= i and i < this.length) then + if not (0 <= i and i < self.length) then return nil end -- fetch a new slice starting at i, using provided fetch() local start - this.slice, start = this.fetch(this.key, i) + self.slice, start = self.fetch(self.key, i) -- It's possible that caller-provided fetch() function forgot -- to return the adjusted start index of the new slice. In -- Lua, 0 tests as true, so if fetch() returned (slice, 0), - -- we'll duly reset this.start to 0. Otherwise, assume the + -- we'll duly reset self.start to 0. Otherwise, assume the -- requested index was not adjusted: that the returned slice -- really does start at i. - this.start = start or i + self.start = start or i -- Hopefully this slice contains the desired i. -- Back to 1-relative indexing. - return this.slice[i - this.start + 1] + return self.slice[i - self.start + 1] end, -- We purposely avoid putting any array entries (int keys) into -- our table so that access to any int key will always call our @@ -40,7 +42,7 @@ local mt = { -- This result set provides read-only access. -- We do not support pushing updates to individual items back to -- C++; for the intended use cases, that makes no sense. - __newindex = function(this, i, value) + __newindex = function(self, i, value) error("result_view is a read-only data structure", 2) end } @@ -50,20 +52,47 @@ local mt = { -- access to the entire result set. -- key_length: {result set key, total result set length} -- fetch: function(key, start) that returns (slice, adjusted start) -local function result_view(key_length, fetch) - return setmetatable( - { - key=key_length[1], - length=key_length[2], - -- C++ result sets use 0-based indexing, so internally we do too - start=0, - -- start with a dummy array with length 0 - slice={}, - fetch=fetch - }, - -- use our special metatable - mt - ) -end +local result_view = setmetatable( + { + -- generic fetch() function + fetch = function(key, start) + local fetched = leap.request( + 'LLInventory', + {op='getSlice', result=key, index=start}) + return fetched.slice, fetched.start + end, + -- generic close() function accepting variadic result-set keys + close = function(...) + local keys = table.pack(...) + -- table.pack() produces a table with an array entry for every + -- parameter, PLUS an 'n' key with the count. Unfortunately that + -- 'n' key bollixes our conversion to LLSD, which requires either + -- all int keys (for an array) or all string keys (for a map). + keys.n = nil + leap.send('LLInventory', {op='closeResult', result=keys}) + end + }, + { + -- result_view(key_length, fetch) calls this + __call = function(class, key_length, fetch) + return setmetatable( + { + key=key_length[1], + length=key_length[2], + -- C++ result sets use 0-based indexing, so internally we do too + start=0, + -- start with a dummy array with length 0 + slice={}, + -- if caller didn't pass fetch() function, use generic + fetch=fetch or class.fetch, + -- returned view:close() will close result set with passed key + close=function(self) class.close(key_length[1]) end + }, + -- use our special metatable + mt + ) + end + } +) return result_view -- cgit v1.2.3 From a8dd7135f0423384dbbb1e3b98514149c6a69e6b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 3 Sep 2024 20:50:18 -0400 Subject: Use Lua result-set logic for "LLFloaterReg"s "getFloaterNames" op. This is the query that produced so many results that, before we lifted the infinite-loop interrupt limit, inspect(result) hit the limit and terminated. --- indra/newview/scripts/lua/require/UI.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'indra/newview/scripts') diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua index bbcae3514a..aa64c0c7f9 100644 --- a/indra/newview/scripts/lua/require/UI.lua +++ b/indra/newview/scripts/lua/require/UI.lua @@ -2,6 +2,7 @@ local leap = require 'leap' local mapargs = require 'mapargs' +local result_view = require 'result_view' local Timer = (require 'timers').Timer local util = require 'util' @@ -234,7 +235,12 @@ function UI.closeAllFloaters() end function UI.getFloaterNames() - return leap.request("LLFloaterReg", {op = "getFloaterNames"}).floaters + local key_length = leap.request("LLFloaterReg", {op = "getFloaterNames"}).floaters + local view = result_view(key_length) + return LL.setdtor( + 'registered floater names', + view, + function(self) view:close() end) end return UI -- cgit v1.2.3 From 35c3f0227c334e059abdc36c36cc942a517d92ec Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 4 Sep 2024 07:43:48 -0400 Subject: Instead of traversing all calling cards, pick a selected few. Make test_LLInventory.lua directly select from the calling_cards result set, instead of first copying all names to a separate array. --- indra/newview/scripts/lua/test_LLInventory.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'indra/newview/scripts') diff --git a/indra/newview/scripts/lua/test_LLInventory.lua b/indra/newview/scripts/lua/test_LLInventory.lua index 918ca56a2e..de57484bcd 100644 --- a/indra/newview/scripts/lua/test_LLInventory.lua +++ b/indra/newview/scripts/lua/test_LLInventory.lua @@ -15,9 +15,10 @@ calling_cards_id = LLInventory.getBasicFolderID('callcard') calling_cards = LLInventory.getDirectDescendents(calling_cards_id).items -- Print a random calling card name from 'Calling Cards' folder -local card_names = {} -for _, value in pairs(calling_cards) do - table.insert(card_names, value.name) -end +-- (because getDirectDescendents().items is a Lua result set, selecting +-- a random entry only fetches one slice containing that entry) math.randomseed(os.time()) -print("Random calling card: " .. inspect(card_names[math.random(#card_names)])) +for i = 1, 5 do + pick = math.random(#calling_cards) + print(`Random calling card (#{pick} of {#calling_cards}): {calling_cards[pick].name}`) +end -- cgit v1.2.3 From d67ad5da3b5a37f7b4cb78e686ae36f31c513153 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 4 Sep 2024 09:15:10 -0400 Subject: `result_view()`'s table's `close()` method need not be further wrapped. `LL.setdtor(desc, table, func)` eventually calls `func(table)`. So the `close()` method on the table returned by `result_view()` can be directly passed to `setdtor()`, instead of wrapped in a new anonymous function whose only job is to pass the table to it. Moreover, there's no need for the table returned by LLInventory.lua's `result()` function to lazily instantiate the `result_view()` for `categories` or `items`: neither `result_view` will fetch a slice unless asked. Just return `{categories=result_view(...), items=result_view(...), close=...}`. This dramatically simplifies the `result()` function. Since that table also defines a `close()` function, that too can be passed directly to `setdtor()` without being wrapped in a new anonymous function. --- indra/newview/scripts/lua/require/LLInventory.lua | 52 +++++------------------ indra/newview/scripts/lua/require/UI.lua | 5 +-- 2 files changed, 12 insertions(+), 45 deletions(-) (limited to 'indra/newview/scripts') diff --git a/indra/newview/scripts/lua/require/LLInventory.lua b/indra/newview/scripts/lua/require/LLInventory.lua index 9cf72a8678..2c80a8602b 100644 --- a/indra/newview/scripts/lua/require/LLInventory.lua +++ b/indra/newview/scripts/lua/require/LLInventory.lua @@ -3,48 +3,18 @@ local mapargs = require 'mapargs' local result_view = require 'result_view' local function result(keys) - return LL.setdtor( - 'LLInventory result', - setmetatable( - -- the basic table wrapped by setmetatable just captures the int - -- result-set {key, length} pairs from 'keys', but with underscore - -- prefixes - { - _categories=keys.categories, - _items=keys.items, - -- call result:close() to release result sets before garbage - -- collection or script completion - close = function(self) - result_view.close(self._categories[1], self._items[1]) - end - }, - -- The caller of one of our methods that returns a result set - -- isn't necessarily interested in both categories and items, so - -- don't proactively populate both. Instead, when caller references - -- either 'categories' or 'items', the __index() metamethod - -- populates that field. - { - __index = function(t, field) - -- we really don't care about references to any other field - if not table.find({'categories', 'items'}, field) then - return nil - end - -- We cleverly saved the result set {key, length} pair in - -- a field with the same name but an underscore prefix. - local view = result_view(t['_' .. field]) - -- cache that view for future reference - t[field] = view - return view - end - } - ), - -- When the table-with-metatable above is destroyed, tell LLInventory - -- we're done with its result sets -- whether or not we ever fetched - -- either of them. - function(res) - res:close() + -- capture result_view() instances for both categories and items + local result_table = { + categories=result_view(keys.categories), + items=result_view(keys.items), + -- call result_table:close() to release result sets before garbage + -- collection or script completion + close = function(self) + result_view.close(keys.categories[1], keys.items[1]) end - ) + } + -- When the result_table is destroyed, close its result_views. + return LL.setdtor('LLInventory result', result_table, result_table.close) end local LLInventory = {} diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua index aa64c0c7f9..73a76fa6b8 100644 --- a/indra/newview/scripts/lua/require/UI.lua +++ b/indra/newview/scripts/lua/require/UI.lua @@ -237,10 +237,7 @@ end function UI.getFloaterNames() local key_length = leap.request("LLFloaterReg", {op = "getFloaterNames"}).floaters local view = result_view(key_length) - return LL.setdtor( - 'registered floater names', - view, - function(self) view:close() end) + return LL.setdtor('registered floater names', view, view.close) end return UI -- cgit v1.2.3