Reputation: 131
I've run into the age old unpack bug, where I have an array in Lua that can contain nil values, and I want to unpack the array with the nil values; which seems is not possible. What is the alternative to this logic?
Here is the code I am attempting to run
function InputSystem:poll(name, ...)
local system = self:findComponent(name)
local values, arr = {...}, {}
for i, v in pairs(values) do
arr[#arr+1] = system[v]
end
--If the first guy is null this does not work!! WHY
return unpack(arr, 1, table.maxn(values))
end
The idea is I poll my input system dynamically so that I only return the values I want like so:
local dragged,clicked,scrolled = self.SystemManager:findSystem('Input'):poll('Mouse', 'Dragged', 'Clicked', 'Scrolled')
Any thoughts? Thanks
EDIT:
I seem to not understand Lua fully. I was wanting to return the same amount of variables as passed in by ..., but in the loop if the property is not found I thought it would set it to nil, but this seems to be wrong.
function InputSystem:poll(name, ...)
local system = self:findComponent(name)
local values, arr = {...}, {}
for i, v in pairs(values) do
arr[#arr+1] = system[v] --If not found set nil
end
--I want this to return the length of ... in variables
--Example I pass 'Dragged', 'Clicked' I would want it to return nil {x:1,y:1}
return unpack(arr, 1, table.maxn(values))
end
Clearly I am a Lua master...
Upvotes: 0
Views: 4504
Reputation: 7064
for i, v in pairs(values) do
arr[#arr+1] = system[v] -- This doesn't work!
end
The problem with your implementation is that you expect appending nil to an array to increase it's length, which it doesn't:
local arr = {1, 2, 3}
print(#arr) --> 3
arr[#arr+1]=nil
print(#arr) --> 3
What you want is, essentially, a map
function, that takes a list of elements, applies a function fn
to each of them and returns the list of the results.
Normally, this can easily be implemented as a tail-recursive function like this:
local function map(fn, elem, ...)
if elem then return fn(elem), map(fn, ...)
end
This doesn't deal well with nil
arguments though, as they would make the condition false while there are still arguments left to handle, but we can modify it using select
to avoid this:
local function map(fn, elem, ...)
if select('#', ...)>0 then return fn(elem), map(fn, ...)
else return fn(elem) end
end
-- This implementation still gets TCOd :)
Then you can use it like this:
map(string.upper, 'hello', 'world') --> 'HELLO', 'WORLD'
You want to map each value in ...
to the corresponding value in a table, but since map
takes a function as its first value, we can just wrap that in a function. And because the table isn't known to us at the time of writing the code, we have to generate the function at runtime:
local function index(table)
return function(idx)
return table[idx]
end
end
Now we can do this:
map(index{'hello', 'world'}, 1, 2) --> 'hello', 'world'
-- index{'hello', 'world'} returns a function that indexes the given table
-- with its first argument and returns the value
Then you can write your InputSystem
function like this:
function InputSystem:poll(name, ...)
return map(index(self:findComponent(name)), ...)
end
Obviously, we don't need that generic map function in this case, since we're always indexing a table. We can rewrite map to use a table like this:
local function map(tab, elem, ...)
if select('#', ...)>0 then return tab[elem], map(tab, ...)
else return tab[elem] end
end
and the main function will become:
function InputSystem:poll(name, ...)
return map(self:findComponent(name), ...)
end
One more thing I noticed:
for i, v in pairs(values) do
arr[#arr+1] = system[v] --If not found set nil
end
pairs
iterates out of order, so your line for i, v in pairs(values) do
may very well completely re-order the values. Since further down you write local dragged,clicked,scrolled = self.SystemManager:findSystem...
I believe you expect the return values to remain in order.
Upvotes: 0
Reputation: 10939
You should use table.pack
and table.unpack
to preserve nil
s. If you use Lua 5.2 or above you can remove the compatibility snippet.
-- Backwards compatibility
table.pack = table.pack or function(...) return { n = select("#", ...), ... } end
table.unpack = table.unpack or unpack
function test(...)
local values = table.pack(...)
local arr = {}
for i, v in pairs(values) do
-- iterates only the non-nil fields of "values"
arr[i] = 10*v
end
return table.unpack(arr, 1, values.n)
end
print(test(nil, 1, nil, 2, nil, nil, 3))
$ lua5.3 test.lua
nil 10 nil 20 nil nil 30
$ lua5.2 test.lua
nil 10 nil 20 nil nil 30
$ lua5.1 test.lua
nil 10 nil 20 nil nil 30
$ luajit test.lua
nil 10 nil 20 nil nil 30
Upvotes: 3