User Data Environment Examples |
|
Here's the basic userdata structure:
// How do you get the wiki to syntax colour C? typedef struct lvec_s { #ifdef VERSION51 int has_env; #else int len; #endif lua_Number d[1]; } lvec_t;
has_env
member. Aside from padding the userdata header to a doubleword boundary on x86 architecture, which prevents the doubles from crossing cache boundaries, this is used as a flag to indicate whether or not the object has a useful environment table. (In version 5.0, the len
member would throw alignment off. It would be better to use union {int len; lua_Number dummy} u;
)
The Lua API does not allow the creation of a userdata without an environment table and all the other solutions I found to check for its existence were slower.
Here's the getter method, which expects to find the object's method table at upvalue index 2 (I use upvalue 1 for the metatable in all methods):
/* getter and setter, allowing overriding in 5.1. 5.0 doesn't have environment tables */ static int lvec_meta_index (lua_State *L) { int len = 0; lvec_t *u = getveclen(L, 1, &len);
if (lua_type(L, 2) == LUA_TNUMBER) { int idx = checkkey(L, len); if (idx) { lua_pushnumber(L, u->d[idx-1]); return 1; } else return 0; }
if (lua_type(L, 2) == LUA_TSTRING) { size_t slen; const char *key = lua_tolstring(L, 2, &slen); if (slen == 1) { int idx = key[0] - 'x'; if (len <= 3 && idx >= 0 && idx < len) { lua_pushnumber(L, u->d[idx]); return 1; } } }
__index
metamethod of the environment table, once the environment table is created, which means that the final lookup can be done only once, and also allows the possibility to create subclasses of the object in Lua.
#ifdef VERSION51 if (u->has_env) { lua_getfenv(L, 1); lua_pushvalue(L, 2); lua_gettable(L, -2); if (!lua_isnil(L, -1)) return 1; lua_pop(L, 2); } #endif
__index
function (for speed of lookup). If it's not found in this table, we just return the nil
lua_gettable(L, lua_upvalueindex(2)); return 1; }
The __newindex
method has slightly different logic. Again, the first check is for numeric and short string keys, but in this case, errors are thrown both for bad numeric keys and for bad values. (The call to luaL_checknumber
is not correct, I was being lazy: it will get fixed at some point. It produces hard-to-interpret error messages.) If that fails, then an environment table is created if necessary, and the key/value pair inserted in the newly-created table. (As mentioned above, I would normally attach the method table as an __index
meta when creating the new environment table.)
static int lvec_meta_newindex (lua_State *L) { int len = 0; lvec_t *u = getveclen(L, 1, &len); if (lua_type(L, 2) == LUA_TNUMBER) { int idx = checkkey(L, len); if (idx) u->d[idx-1] = luaL_checknumber(L, 3); else luaL_error(L, "Vector index not integer or out of range"); return 0; } if (lua_type(L, 2) == LUA_TSTRING) { size_t slen; const char *key = lua_tolstring(L, 2, &slen); if (slen == 1) { int idx = key[0] - 'x'; if (len <= 3 && idx >= 0 && idx < len) { u->d[idx] = luaL_checknumber(L, 3); return 0; } } } #ifdef VERSION51 if (!u->has_env) { lua_newtable(L); lua_pushvalue(L, -1); lua_setfenv(L, 1); u->has_env = 1; } else lua_getfenv(L, 1); lua_replace(L, 1); lua_settable(L, 1); #else else luaL_error(L, "Vector index not integer or out of range"); #endif return 0; }