table.insert doesn't trigger __index? - lua

I made a custom table using metatables that automatically tracks sizing when elements are added. It works very well and removes the need for the # operator or the getn function.
However it has a problem. If you call table.insert, the function apparently never calls __index or __newindex. Thus, my table cannot know when elements are removed this way. I assume the problem is the same with table.remove as well.
How can I either:
Capture the event of insert and use my own function to do so
Throw an error if insert is called on my table.
Thanks
function Table_new()
local public = { }
local tbl = { }
local size = 0
function public.size()
return size
end
return setmetatable(public, {
__newindex = function(t, k, v)
local previous_v = tbl[k]
rawset(tbl, k, v)
if previous_v ~= nil then
if v == nil then
size = size - 1
end
elseif v ~= nil then
size = size + 1
end
end,
__index = tbl
})
end
local t = Table_new()
t[5] = "hi"
t[17] = "hello"
t[2] = "yo"
t[17] = nil
print(t.size()) -- prints 2
local z = Table_new()
table.insert(z, "hey")
table.insert(z, "hello")
table.insert(z, "yo")
print(z.size()) -- prints 1 -- why?

If you print k,v in __newindex, you'll see that k is always 1. This is because table.insert asks for the size of table to find where to insert the value. By default, it's at the end. You should add a __len metamethod. But perhaps this defeats your purposes (which are obscure to me).

Related

Check If Metatable Is Read Only

I'm trying to find a way to check if a Metatable is readonly or not
for example
local mt = metatable(game)
if mt == "readonly" do
print("Attempt to modify Metatables")
end
I hope there is a way to do this for Roblox, so I can prevent GUI tampering
You can use getmetatable() to see if the contents of a metatable are protected.
Example:
local mt = getmetatable(game)
if mt ~= nil and type(mt) ~= "table" then -- not perfect, as __metatable could be set to {}
print("This metatable is protected!")
end
Alternatively if you are looking to see if the table itself is read-only, you will need to check two behaviors
What happens when you attempt to add a value to the table
What happens when you attempt to change a value in the table.
Example of read-only table:
local protected_table = {'1', '2', '3'}
local table = setmetatable({}, {-- create a dumby table with a metatable
__index = function(_, k)
return protected_table[k]-- access protected table
end,
__newindex = function()
error('This value is read-only.')
end,
__pairs = function(_)
return function(_, k)
return next(protected_table, k)
end
end,
__metatable = false,
})
Examples of possible interactions:
table[4] = "4" -- read-only error will be generated by _newindex setting
table[1] = "0" -- read-only error will be generated by _newindex setting
first = table[1] -- will retrieve first value of protected_table

Division of metatable

got some problem with metatable. This is my simple metatable:
local mt = {}
function mt:add(n)
return setmetatable({n = n}, {__index = mt})
end
function mt:get() return self.n end
Now I want to add some division like:
mt.math
mt.effect
Which each one has some own methods like:
mt.math:floor() return math.floor(self:get()) end
mt.effect:show(args) onMapShowEffect(self:get(), {x = x + (args[1] ~= nil or 0), ...) end
mt.effect:get() return getCurrentPos() end
Any ideas?
OK, trying make all details to share my problem.
Player = {}
function Player:add(this)
return setmetatable({this = this}, {__index = Player})
end
Player:get() return self.this end
Above code works perfectly on this example
function enterToGame(player1, player2)
local p1 = Player:add(player1)
local p2 = Player:add(player2)
print(p1:get()) -- ID1
print(p2:get()) -- ID2
Now I want to create some helpfully methods(functions) for table Player. I want to make it more flexible, so I want divide it for classes. Example:
Player.info = {
id = function() return Player:get() end,
}
Player.pos = {
get = function() return getPosition(Player:get()) end,
set = function(args) setPosition(Player:get(), args) end,
}
Player.speed = {
get = function() return getSpeed(Player:get()) end,
set = function(value) setSpeed(value) end,
improve = function(value) setSpeed(Player.speed.get() + value) end,
}
But its not work exactly what I want:
function enterToGame(player1, player2)
local p1 = Player:add(player1)
local p2 = Player:add(player2)
print(p1:get()) -- ID1
print(p2:get()) -- ID2
print(p1.info.id()) -- ID2 instead of ID1
print(p2.info.id()) -- ID2
When I put Player:get() in my methods its return last object declaration.
Based on what you state, if you do
mt.math = mt:add(123)
You don't need themt:get() because mt is the metatable for mt.math. Then
mt.math.floor = function(self) return math.floor(self.n) end
will work as expected. For example,
print(mt.math:floor())
prints 123.
EDIT 1: So now that I have a better understanding of what you are trying to do: normally you would do
p1:id()
p1:getPos()
p1:setPos()
p1:getSpeed()
p1:improveSpeed()
Note the colon, this is important, so that each method gets a "self" as first parameter, thereby given them the table instance to operate on (p1, in the above example). Instead you want to group methods so
p1.info:id()
p1.pos:get()
p1.pos:set()
p1.speed:improve()
p1.speed:get()
These methods will get a self that points to p1.info, p1.pos, etc. But those sub-tables have no knowledge of the container table (p1). The info and pos tables are in the Player class: they are shared by all instances of Player (p1, p2 etc). You have to make the info and pos tables non-shared:
function Player:add(player)
local pN= setmetatable( {n = player, info={}, pos={}}, {__index = Player})
pN.info.id = function() return pN.n end
pN.pos.set = function(x) return setPosition(pN, x) end
return pN
end
Then you get
> p1=mt:add(player1)
> p2=mt:add(player2)
> print(player1)
table: 0024D390
> print(p1.info.id())
table: 0024D390
> print(player2)
table: 0024D250
> print(p2.info.id())
table: 0024D250
All that said, I don't really like the idea of having to use closures like this, perhaps there are gotchas since not everything will be in Player.

Lua event handler

Does lua have an "event handler" build in or does it have a lib available to do that?
So that, an example, when "a = 100" an event happens.
Something else rather than using:
while true do
if a == 100 then
[...]
break;
end
end
Or simply adding a sleep to it. The "while true do" is just an example but its a terrible one.
Lua operates in a single-thread, so any checking must be explicitly performed by your code.
The act of performing code immediately upon a variable changing is known as "watching".
If you are programming in an environment where a set of code is run every frame (such as a game), you can check manually.
For example:
WatchedVariables = {
a = 5,
b = 22,
}
WatchedVariables_cache = {}
for k,v in pairs(WatchedVariables) do
WatchedVariables_cache[k] = v
end
function OnFrame()
print("NEXT FRAME! (possibly 1 second later or something)")
for k,v in pairs(WatchedVariables) do
local v_old = WatchedVariables_cache[k]
if v ~= v_old then
-- this is the "callback"
print(tostring(k).." changed from "..tostring(v_old).." to "..tostring(v))
WatchedVariables_cache[k] = v
end
end
end
function SomeFunctionThatOperatesSomeTime()
print("about to change a, brother!")
WatchedVariables.a = -7
print("a is changed")
end
Upon the next frame, the callback code (the print) will be executed.
The disadvantage of this approach is that the callback code is not printed immediately after WatchedVariables.a is set to -7, that is: the output will be:
about to change a, brother!
a is changed
NEXT FRAME! (possibly 1 second later or something)
a changed from 5 to -7
To prevent this potentially undesired behaviour, a setter function can be used, for example:
MyObject = {
_private_a = 5,
set_a = function(self, new_value_of_a)
self._private_a = 5
-- callback code
print("a set to "..tostring(new_value_of_a))
end,
get_a = function(self)
return self._private_a
end
}
function SomeFunctionThatOperatesSomeTime()
print("about to change a, brother!")
MyObject:set_a(-7)
print("a is changed")
end
The output of this code shows that the callback runs immediately:
about to change a, brother!
a set to -7
a is changed
To make this more comfortable, Lua provides metatables that make such behaviour transparent to the programmer.
Example:
MyObject = {
__privates = {
a = 5,
}
}
MyObject_meta = {
__index = function(self, k)
return rawget(self, "__privates")[k]
end,
__newindex = function(self, k, v)
rawget(self, "__privates")[k] = v
-- callback code
print("a set to "..tostring(v))
end,
}
setmetatable(MyObject, MyObject_meta)
function SomeFunctionThatOperatesSomeTime()
print("about to change a, brother!")
MyObject.a = -7
print("a is changed")
end
The output of this code will be the same as the previous example:
about to change a, brother!
a set to -7
a is changed
Here's an implementation for your example case:
MyObject = {
__privates = {
a = 5,
}
__private_callback = function(self, k, ov, v)
if k == "a" and v == "100" then
print("a is 100!")
end
end
}
MyObject_meta = {
__index = function(self, k)
return rawget(self, "__privates")[k]
end,
__newindex = function(self, k, v)
local privates = rawget(self, "__privates")
local ov = privates[k]
privates[k] = v
rawget(self, "__private_callback")(self, k, ov, v)
end,
}
setmetatable(MyObject, MyObject_meta)
function SomeFunctionThatOperatesSomeTime()
MyObject.a = -7 -- prints nothing
MyObject.a = 100 -- prints "a is 100!"
MyObject.a = 22 -- prints nothing
end
Why are the variables __privates and __private_callback prefixed with two underscores? It is convention to prefix private members that should not be accessed in typical programming situations with two underscores. If you familiar with object-oriented methodology and it's implementation in languages such as Java and C++, you will understand how it is similar to the private and protected keywords.
If you are familiar with the C# language, you may see how the set_a/get_a and metatable implementations are similar to accessors (set/get).

Chain Lua metatables

I am have a situation where two libraries L,M, are trying to set a metatable for _G (named mL, mM respectively). The only thing in the metatables are __index.
How can I chain these two metatables so that if the __index in one fails it calls the index in the other?
Have one metatable that stores both mL and mM, and if one returns nil, check the other:
local metatbl = {}
metatbl.tbls = {mL, mM};
function metatbl.__index(intbl, key)
for i, mtbl in ipairs(metatbl.tbls) do
local mmethod = mtbl.__index
if(type(mmethod) == "function") then
local ret = mmethod(table, key)
if ret then return ret end
else
if mmethod[key] then return mmethod[key] end
end
return nil
end
end
setmetatable(_G,metatbl)
Assuming there's a point where your code can fiddle with _G's metatable itself, after the libraries have mucked about, to fix what L and M did, you can just stick in your own metatable that does the combined search, e.g.:
combined_metatable = {
__index = function (t, k)
return mL.__index (t, k) or mM.__index (t, k)
end
}
setmetatable (_G, combined_metatable)
That has the advantage of not fiddling with mL or mM themselves.
If you don't have the opportunity to correct things after the fact, you could just modify the __index entries of the library metatables to do the combined search:
local original_mM_index = mM.__index
local original_mL_index = mL.__index
local function L_then_M_index (t, k)
return original_mL_index (t, k) or original_mM_index (t, k)
end
mL.__index = L_then_M_index
mM.__index = L_then_M_index
[Note that as both library metatables are modified, this will work whichever gets installed last ("winning" the competition).]
Use __metatable to give them a table that isn't actually the metatable or give the library a different setmetatable: that way they can't change your _G metatable.
getmetatable(getfenv()).__metatable = function ( o ) return { } end
OR
local orig_setmetatable = setmetatable
function setmetatable ( ob , mt )
if ob == getfenv() or ob == _G then
return ob
else
return orig_setmetatable(ob,mt)
end
end
(depending on how the library does things)
If you still want to track the things it does to the metatable; look through mt before the return ob (and if you actually wanted to chain __index lookups; add to a table):
local env_indexes = {}
setmetatable(_G,{__index=function(t,k) for i,m in ipairs(env_indexes) do local v=m[k]; if v then return v end end return nil end } )
local orig_setmetatable = setmetatable
function setmetatable ( ob , mt )
if ob == _G then
table.insert ( env_indexes , mt.__index )
return ob
else
return orig_setmetatable(ob,mt)
end
end
Otherwise this is very bad practice for libraries to be doing; tell the author not to!

How can I change every index into a table using a metatable?

I'm trying to write a metatable so that all indexes into the table are shifted up one position (i.e. t[i] should return t[i+1]). I need to do this because the table is defined using index 1 as the first element, but I have to interface with a program that uses index 0 as the first element. Since reading Programming in Lua, I think that I can accomplish what I want with a proxy table, but I can't seem to get it working. So far, I have this:
t = {"foo", "bar"}
local _t = t
t = {}
local mt = {
__index = function(t, i)
return _t[i+1]
end
}
setmetatable(t, mt)
However, this does not create the expected result. In fact, it doesn't return any values at all (every lookup is nil). Is there a better way to do this, or am I just missing something?
t = {"foo", "bar"}
local _t = t
t = {}
local mt = {
__index = function(t, i)
return _t[i+1]
end
}
setmetatable(t, mt)
print(t[0])
outputs "foo" for me when run here: http://www.lua.org/cgi-bin/demo

Resources