Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are
spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the
password reset link.
Due to spam on this forum, all posts now need moderator approval.
Entire forum
➜ MUSHclient
➜ Lua
➜ Metatable not working, and I can't fix it
|
Metatable not working, and I can't fix it
|
It is now over 60 days since the last post. This thread is closed.
Refresh page
| Posted by
| Larkin
(278 posts) Bio
|
| Date
| Wed 21 Mar 2007 02:53 PM (UTC) |
| Message
| I've been scratching my head all morning trying to figure out where I'm going wrong with this bit of code. Basically, I'm borrowing the idea from Ked to use GetInfo(232) as a method of expiring balances in a failsafe. (I'm sure my logic is confused in my scripts overall, but that's a separate issue.)
Here's what I'm using for my metatable, and I'm not seeing the expected values and notes when I set/get things. Any ideas?
lost_bals = {}
setmetatable(lost_bals,
{
__index =
function (t, name)
local b = rawget(t, name)
Note("Table " .. tostring(t) .. ", key " .. name .. " has value " .. tostring(b))
if (not b) then
return false
end
if (b <= GetInfo(232)) then
t[name] = nil
return false
end
return true
end,
__newindex =
function(t, name, val)
if (val == nil) then
Note("Table " .. tostring(t) .. ", key " .. name .. " deleted")
rawset(t, name, nil)
return
end
local v = (val or 5.0) + GetInfo(232)
Note("Table " .. tostring(t) .. ", key " .. name .. " gets value " .. tostring(v))
rawset(t, name, v)
end
})
| | Top |
|
| Posted by
| David Haley
USA (3,881 posts) Bio
|
| Date
| Reply #1 on Wed 21 Mar 2007 07:20 PM (UTC) |
| Message
| | These look reasonable a priori; how are you using it? And what are you expecting to see? |
David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone
http://david.the-haleys.org | | Top |
|
| Posted by
| Nick Gammon
Australia (23,166 posts) Bio
Forum Administrator |
| Date
| Reply #2 on Wed 21 Mar 2007 08:23 PM (UTC) |
| Message
| I am guessing you are trying to store a "time when something expires", based on the current timer plus a number of seconds you supply? I hope that is it.
This line concerns me:
local v = (val or 5.0) + GetInfo(232)
You know val is not nil because you have a test just above. Thus it can just read:
local v = val + GetInfo(232)
The next problem is that _newindex is only called if the entry in the table does not exist. Thus, after you have set the time once, subsequent attempts to change it will simply store the value, as _newindex will not be called.
For example, in my test, initially it stored a large number, and then simply the supplied argument:
"nick"=215098.24469088
"nick"=22
Similarly, _index is only called if the entry does not exist, it isn't called every time. Thus, attempting to access a value (which already exists) will simply return the value. You need to keep the table empty for your idea to work. Then of course you have the issue of where to store the data. So, we make a second "proxy" table, which really stores the data, and the main table is the one with the metatable on it:
lost_bals = lost_bals or {}
lost_bals_proxy = lost_bals_proxy or {}
setmetatable(lost_bals,
{
__index =
function (t, name)
local b = lost_bals_proxy [name]
Note("Table " .. tostring(t) .. ", key " .. name .. " has value " .. tostring(b))
if (not b) then
return false
end
if (b <= GetInfo(232)) then
lost_bals_proxy [name] = nil
return false
end
return true
end,
__newindex =
function(t, name, val)
if (val == nil) then
Note("Table " .. tostring(t) .. ", key " .. name .. " deleted")
lost_bals_proxy [name] = nil
return
end
local v = val + GetInfo(232)
Note("Table " .. tostring(t) .. ", key " .. name .. " gets value " .. tostring(v))
lost_bals_proxy [name] = v
end
})
Now if I do something like this:
And then this every second or so:
I will see this:
Table table: 00D79CE0, key nick has value 215995.74794162
true
Table table: 00D79CE0, key nick has value 215995.74794162
true
.... 11 seconds later:
Table table: 00D79CE0, key nick has value 215995.74794162
false
Table table: 00D79CE0, key nick has value nil
false
Notice that for 11 seconds it now returns the original value (timer + 11), and after 11 seconds it realises the time is up and returns false. Next time around the table item is not even there, so it says it has the value nil.
|
- Nick Gammon
www.gammon.com.au, www.mushclient.com | | Top |
|
| Posted by
| David Haley
USA (3,881 posts) Bio
|
| Date
| Reply #3 on Wed 21 Mar 2007 08:32 PM (UTC) |
| Message
| | Oh yeah... *bonk self* I'd forgotten that the indexing metamethods only apply to keys that aren't in there yet. I've only ever used them proxy-style and haven't used them as was done here in so long that I'd forgotten it mattered. :-) |
David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone
http://david.the-haleys.org | | Top |
|
| Posted by
| Nick Gammon
Australia (23,166 posts) Bio
Forum Administrator |
| Date
| Reply #4 on Wed 21 Mar 2007 08:33 PM (UTC) |
| Message
| By the way, putting "if" test conditions in brackets is a rather C way of doing things, it isn't necessary in Lua. So, the code could be neatened up a bit:
lost_bals = lost_bals or {}
lost_bals_proxy = lost_bals_proxy or {}
setmetatable(lost_bals,
{
__index =
function (t, name)
local b = lost_bals_proxy [name]
TraceOut ("Table key " .. name .. " has value " .. tostring(b))
if not b then
return false
end
if b <= GetInfo(232) then
lost_bals_proxy [name] = nil
return false
end
return true
end,
__newindex =
function(t, name, val)
if val == nil then
TraceOut ("Table key " .. name .. " deleted")
lost_bals_proxy [name] = nil
return
end
local v = val + GetInfo(232)
TraceOut ("Table key " .. name .. " gets value " .. tostring(v))
lost_bals_proxy [name] = v
end
})
I have also converted Note to TraceOut in this version, because TraceOut only echoes its message if Trace is on - it is like an optional debug message. That is handy to use when you occassionally want to see your debug messages. |
- Nick Gammon
www.gammon.com.au, www.mushclient.com | | Top |
|
| Posted by
| Nick Gammon
Australia (23,166 posts) Bio
Forum Administrator |
| Date
| Reply #5 on Wed 21 Mar 2007 09:33 PM (UTC) |
| Message
| You could neaten things up a bit more by putting the proxy table inside the main table. This assumes you would never need the key _proxy, and uses rawget and rawset to make the proxy table if it doesn't exist yet. This method is a bit nicer because you could then add the metatable to lots of tables, without having to have separate proxy tables maintained for each one:
lost_bals = lost_bals or {}
function get_proxy_table (t)
-- create proxy table if it doesn't exist yet
if not rawget (t, "_proxy") then
rawset (t, "_proxy", {})
end -- if no proxy table yet
-- now return the proxy table
return t._proxy
end -- function get_proxy_table
setmetatable(lost_bals,
{
__index =
function (t, name)
local proxy = get_proxy_table (t)
local b = proxy [name]
TraceOut ("Table key " .. name .. " has value " .. tostring(b))
if not b then
return false
end
if b <= GetInfo(232) then
proxy [name] = nil
return false
end
return true
end,
__newindex =
function(t, name, val)
local proxy = get_proxy_table (t)
if val == nil then
TraceOut ("Table key " .. name .. " deleted")
proxy [name] = nil
return
end
local v = val + GetInfo(232)
TraceOut ("Table key " .. name .. " gets value " .. tostring(v))
proxy [name] = v
end
})
|
- Nick Gammon
www.gammon.com.au, www.mushclient.com | | Top |
|
| Posted by
| Larkin
(278 posts) Bio
|
| Date
| Reply #6 on Thu 22 Mar 2007 06:32 PM (UTC) Amended on Thu 22 Mar 2007 06:35 PM (UTC) by Larkin
|
| Message
| Wow! So much great feedback! This is what I love about MUSHclient the most. :)
I've read lots of things about how to use metatables, and I don't recall one place that it was mentioned the __index and __newindex only worked for items that are not in the table already. In fact, I thought that was the whole point to having the rawget/rawset functions. Learn something new every day.
Thanks so much for the help!
Edit: I know that I don't need to enclose expressions in if statements with Lua. It's just a style thing for me, and sort of a habit from other languages. It's actually easier for me to read my code with them. | | Top |
|
| Posted by
| David Haley
USA (3,881 posts) Bio
|
| Date
| Reply #7 on Thu 22 Mar 2007 07:28 PM (UTC) |
| Message
| PiL says it quite clearly, and the manual says it too but you have to look a bit:
Quote:
# "index": The indexing access table[key].
function gettable_event (table, key)
local h
if type(table) == "table" then
local v = rawget(table, key)
if v ~= nil then return v end
right here: we only look at __index if v is nil i.e. not in table
h = metatable(table).__index
if h == nil then return nil end
else
h = metatable(table).__index
if h == nil then
error(···);
end
end
if type(h) == "function" then
return h(table, key) -- call the handler
else return h[key] -- or repeat operation on it
end
end
# "newindex": The indexing assignment table[key] = value.
function settable_event (table, key, value)
local h
if type(table) == "table" then
local v = rawget(table, key)
if v ~= nil then rawset(table, key, value); return end
right here: as above
h = metatable(table).__newindex
if h == nil then rawset(table, key, value); return end
else
h = metatable(table).__newindex
if h == nil then
error(···);
end
end
if type(h) == "function" then
return h(table, key,value) -- call the handler
else h[key] = value -- or repeat operation on it
end
end
|
David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone
http://david.the-haleys.org | | Top |
|
| Posted by
| Nick Gammon
Australia (23,166 posts) Bio
Forum Administrator |
| Date
| Reply #8 on Thu 22 Mar 2007 07:34 PM (UTC) Amended on Thu 22 Mar 2007 07:45 PM (UTC) by Nick Gammon
|
| Message
| You are right that the online reference for Lua is fairly silent about the way that __index and __newindex works (as David points out, you have to read the code). However the hard-copy book "Programming in Lua" - available from assorted places - is an excellent read in general, and describes metatables in some depth.
As the book mentions, when looking up a value in a table, if that key is not present, Lua normally returns nil, but first checks for the __index metamethod, and if present, calls that.
Similarly, before creating a new entry in a table, it checks for the __newindex metamethod.
Possible uses for this are:
- __index : implement inheritence (that is, look up a value in a "parent" if not present in the child object), or implement default values (if a value is not present, return some default instead).
- __newindex : redirect writes to a proxy table, or make a table read-only* (by raising an error when you attempt to add to it).
The proxy table idea was used in this post:
http://www.gammon.com.au/forum/?id=4904
Here I made a table with __index and __newindex to redirect attempts to access it, to actually reading/writing MUSHclient world variables.
* - footnote - using __newindex doesn't really make it read-only, because you can change existing entries. However it is useful for checking that you don't inadvertently "pollute" places (like the _G global environment) with variables that are probably best declared local. |
- Nick Gammon
www.gammon.com.au, www.mushclient.com | | Top |
|
| Posted by
| Larkin
(278 posts) Bio
|
| Date
| Reply #9 on Thu 22 Mar 2007 07:35 PM (UTC) |
| Message
| | Okay. That's not what that code says to me. Lua is easier to read than most languages, but some of the tricks do make it a tad obscure sometimes. | | Top |
|
| Posted by
| Nick Gammon
Australia (23,166 posts) Bio
Forum Administrator |
| Date
| Reply #10 on Thu 22 Mar 2007 07:41 PM (UTC) Amended on Thu 22 Mar 2007 07:45 PM (UTC) by Nick Gammon
|
| Message
|
Quote:
I thought that was the whole point to having the rawget/rawset functions.
Actually, the point is to avoid an infinite loop. Say you made some code that used __newindex to do something (eg. check that the data you are adding to a table is valid). Then if it passes the check, you want to really add it. Without rawset, the attempt to really add it would trigger the __newindex again, and you would have a loop. However rawset, bypasses the check and lets you directly add to the table, without triggering metamethods.
Similarly, you may want to use rawget to "really" get the data from a table, not whatever the __index metamethod returns.
In my code mentioned above for the MUSHclient variables, I didn't need to use rawset and rawget, because the data was really being stored somewhere else entirely (the MUSHclient world variable). |
- Nick Gammon
www.gammon.com.au, www.mushclient.com | | Top |
|
| Posted by
| Nick Gammon
Australia (23,166 posts) Bio
Forum Administrator |
| Date
| Reply #11 on Thu 22 Mar 2007 07:47 PM (UTC) |
| Message
| Here is a short example of implementing defaults:
defaults = { hp = 100, mana = 50, movement = 33 }
t = { hp = 20 }
setmetatable (t, { __index = defaults } )
print (t.hp) --> 20
print (t.mana) --> 50
In my example the table t does not have an entry for mana, but the default is pulled from the __index table. This illustrates that you can use an actual table as the __index entry, as well as a function. Thus, defaults can be quickly implemented. |
- Nick Gammon
www.gammon.com.au, www.mushclient.com | | Top |
|
| Posted by
| Larkin
(278 posts) Bio
|
| Date
| Reply #12 on Thu 22 Mar 2007 07:50 PM (UTC) |
| Message
| | I knew that rawget/rawset existed to avoid the infinite loop scenario. I just also thought that they were needed to retrieve the true value of existing keys, not realizing that these metamethods weren't called in that case. So, I had half the knowledge, and now I have the whole picture. My code's working great now. Thanks again! | | Top |
|
The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).
To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.
42,653 views.
It is now over 60 days since the last post. This thread is closed.
Refresh page
top