Metafunction __newindex calling itself

Posted by tobiassjosten on Sat 11 Nov 2006 06:09 PM — 8 posts, 31,314 views.

Sweden #0
I want to create a function to use to initialize variable tables that will serialize themselves whenever something is changes within them. The only way I could think of doing this is with the metafunction __newindex. But since it seems to call itself I am getting a stack overflow error and it halts. Anyone know how do go about doing this?

require "serialize"

function VarInit(name)
	-- Declare and load the table
	_G[name] = {}
	if GetVariable(name) then loadstring(GetVariable(name))() end
	-- Set the metatable to save itself
	setmetatable(_G[name], {
		__index = function(t, k)
			return t['_' .. k]
		end,
		__newindex = function(t, k, v)
			_G[name]['_' .. k] = v
			SetVariable(name, serialize.save(name))
		end 
	})
end
USA #1
The problem is that you create an infinite number of new variables. Let's say you add the variable "foobar". The way your newindex works, it'll create _foobar. But that's a new index, so it creates __foobar... and ___foobar... and so forth.

What you probably want to do is make sure that the first character isn't an underscore before renaming it as you set the value.
Sweden #2
That's how I tried doing it before, but since I wouldn't work I tried this approach, renaming them.
USA #3
Would you mind pasting the code you had before?
Australia Forum Administrator #4
A variation on the technique described in http://www.gammon.com.au/forum/?id=4904 might work for you, as it maintains an empty "proxy" table, and access to the real data are view __newindex.
Sweden #5
Before I had something like ..
require "serialize"

function VarInit(name)
	-- Declare and load the table
	_G[name] = {}
	if GetVariable(name) then loadstring(GetVariable(name))() end
	-- Set the metatable to save itself
	setmetatable(_G[name], {
		__newindex = function(t, k, v)
			SetVariable(name, serialize.save(name))
			_G[name][k] = v
		end 
	})
end


I've resorted to static names for my variables for now, but I'd still like a solution for this if anyone care to point me in a direction.

Using the proxy table to MUSHclient variables wouldn't work for me, since I'd need the variable to exist as a table (for iteration purposes). I could, ofcourse, track the initialized tables and serialize them when I finish my session. But I wanted to avoid doing that. Looks like a dead end to me. :/
USA #6
After reading through the Lua documentation, it appears that you need to use rawset to directly set the table value, bypassing __newindex. If you don't, Lua has no way to realize that you set the field from inside __newindex and will simply call it again.

So you'd do something like this:

	setmetatable(_G[name], {
		__newindex = function(t, k, v)
			SetVariable(name, serialize.save(name))
			rawset(t,k,v)
		end 
	})


(You might want to note that the 't' parameter is the current table. In your old code, even without the stack overflow, it was not a bug but a problem to explicitly refer to the table by name. With the new code, you can create the metatable only once and share it, thus saving some memory, instead of creating one copy per variable.
Sweden #7
Never heard of the rawset function before. I'll try this out asap, thanks alot for the help!