lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


2009/10/23 Chris Gagnon <cgagnon@zindagigames.com>:
> I must be making this harder then it is but I'm not sure how to lay it out.
>
> I have this:
>
> luaL_newmetatable(L, "Vector");
>
> lua_pushliteral(L, "__index");
> lua_pushcfunction(L, LuaVectorGet);
> lua_settable(L, -3);
>
> lua_pushliteral(L, "__newindex");
> lua_pushcfunction(L, LuaVectorSet);
> lua_settable(L, -3);
>
> lua_newtable(L);
> int nFuncIndex = lua_gettop(L);
>
> lua_pushvalue(L, nFuncIndex);
> lua_setfield(L, LUA_GLOBALSINDEX, msc_pType);
>
> lua_newtable(L);
> int mt = lua_gettop(L);
> lua_pushliteral(L, "__call");
> lua_pushcfunction(L, LuaNew);
> lua_pushliteral(L, "new");
> lua_pushvalue(L, -2);     // duplicate LuaNew
> lua_settable(L, nFuncIndex);
> lua_settable(L, mt);
> lua_setmetatable(L, nFuncIndex);
> lua_pop(L, 2);
>
> User data created like so:
> float * fUserData = (float *)lua_newuserdata(L, 4 * sizeof(float));
> luaL_getmetatable(L, "Vector");
> lua_setmetatable(L, -2);
> return fUserData;
>
> This gives me the interface:
> local v = Vector.new(1,1,1)
> local vec = Vector(1,1,2)
> local x = vec.x
>
> the problem is when i want a Length() function
>
> if i switch the __index block to:
>
> lua_pushliteral(L, "__index");
> lua_newtable(L);
>             lua_pushliteral(L, "Length");
>             lua_pushcfunction(L, LuaLength);
>             lua_settable(L, -3);
>
>             lua_pushliteral(L, "__index");
>             lua_pushcfunction(L, LuaGet);
>             lua_settable(L, -3);
>
>             lua_pushvalue(L,-1);
>             lua_setmetatable(L, -2);
> lua_settable(L, nMetatableIndex);
>
> i now get the interface:
> vec:Length()
>
> however the vec.x interface is broken since the __index gets the table on
> the stack not my userdata.
>
> Hopefully that was clear, i appreciate any comments/ help on a fix for this
> or on better ways to build what I'm trying to do.

When I need to define both accessors (e.g. your LuaVectorGet) and
static fields (a classic __index table), I usually use a generic index
function that accesses other fields of the metatable :

int lua__generic___index(lua_State* L)
{
	lua_getmetatable(L, 1);
	lua_getfield(L, -1, "getters");
	if (!lua_isnil(L, -1))
	{
		lua_pushvalue(L, 2);
		lua_gettable(L, -2);
		if (!lua_isnil(L, -1))
		{
			lua_pushvalue(L, 1);
			lua_call(L, 1, 1);
			return 1;
		}
		lua_pop(L, 1); // getter
	}
	lua_pop(L, 1); // getters
	lua_getfield(L, -1, "methods");
	lua_pushvalue(L, 2);
	lua_gettable(L, -2);
	return 1;
}

int lua__generic___newindex(lua_State* L)
{
	lua_getmetatable(L, 1);
	lua_getfield(L, -1, "setters");
	if (!lua_isnil(L, -1))
	{
		lua_pushvalue(L, 2);
		lua_gettable(L, -2);
		if (!lua_isnil(L, -1))
		{
			lua_pushvalue(L, 1);
			lua_pushvalue(L, 3);
			lua_call(L, 2, 0);
			return 0;
		}
		lua_pop(L, 1); // setter
	}
	lua_pop(L, 1); // setters
	return luaL_error(L, "invalid key");
}

Note that the keys of the methods, setters and getters tables can be
any type, this can be useful if you want your v.x getter also work
with v[1].

Here I throw an error when trying to write to an unsupported key (one
without setter), but you could store that new pair in a table
associated to the userdata (for exemple its environment), and return
them from __index. This would allow adding custom attributes from Lua
to a userdata object. Just ask if you would like to see such an
enhanced version.