I have some metatables that reflect some C++ classes/structs. I usually rely on __index to get called for any fields/methods for the object and resolve them in one function.
The difficulty I'm having is when I want to pass parameters to a field, like so:
anim = playerInfo.animations
while anim do
print (anim)
numstates = anim.numstates
for i = 1, numstates do
state = anim.states(i) <--- This line right here is the issue
print(state)
end
anim = anim.next
end
Here is the relevant C code:
static const struct luaL_Reg objanimationlib_m[] = {
{"__tostring", objanimation2string},
{"__index", objanimationget},
{"__newindex", objanimationset},
{NULL, NULL}
};
luaL_newmetatable(L, "objanimation");
lua_pushvalue(L, -1); // duplicates the metatable
luaL_setfuncs(L, objanimationlib_m, 0);
Inside the __index function:
else if (!strcmp(field, "states"))
{
int number = (int)luaL_checknumber(L, 3) - 1; // -1 cuz Lua is not 0-based
if (number >= anim->numstates)
return 0;
PushUserdata(&anim->states[number], "objstate");
}
Running the script, I get an error:
Warning: [string "test.lua"]:13: bad argument #3 to '__index' (number expected, got no value)
I feel like I'm missing something stupidly simple. What is it?
Edit: Here's my solution, inside the __index function:
else if (!strcmp(field, "states"))
{
lua_newtable(L);
int i;
for (i = 0; i < anim->numstates; i++)
{
PushUserdata(&anim->states[i], "objstate");
lua_rawseti(L, -2, i+1);
}
}
This returns a table full of userdata elements. Might be expensive, so this would also increase performance:
anim = playerInfo.animations
while anim do
print (anim)
numstates = anim.numstates
states = anim.states
for i = 1, numstates do
print(states[i])
end
anim = anim.next
end
state = anim.states(i)
is equivalent to
do local f=anim.states; state=f(i) end
and so your metamethod never sees i.
In other words, the index metamethod receives two arguments, the table and the key. What it returns is not necessarily subject to any metamethods, unless you make it explicitly so.
I'd go for defining __len returning numstates and for __call to handle anim.states(i), so that you code can be written
for i = 1, #anim do
state = anim(i)
print(state)
end
Related
I'm trying to add the LUA API to my C++ program, and I'm attempting to allow the script to draw to my GUI. So far, I have this for my lambda function:
auto addToDrawList = [](lua_State* L) -> int
{
int DrawType = (int)lua_tonumber(L, -2);
std::string Label = (std::string)lua_tostring(L, -1);
bool found = false;
for (int i = 0; i <= DrawList.size(); i++)
{
if (DrawList[i].Active == false && !found)
{
switch (DrawType)
{
case(0):
break;
case(1):
DrawList[i].Active = true;
DrawList[i].DrawType = Type::TextBox;
DrawList[i].Label = Label;
break;
}
found = true;
}
}
return 0;
};
This as my LUA script being run:
const char* LUA_FILE = R"(
addToDrawList(1, "Test")
)";
This is how I'm pushing my function to the LUA stack:
lua_State* L = luaL_newstate();
lua_newtable(L);
int uiTableInd = lua_gettop(L);
lua_pushvalue(L, uiTableInd);
lua_setglobal(L, "Ui");
lua_pushcfunction(L, addToDrawList);
lua_setfield(L, -2, "addToDrawList");
The problem is within my first script, as it can't get to the 'DrawList' array as its inside of this.
So, to resolve it, I tried to add this to the lambda's capture list by doing this:
auto addToDrawList = [this](lua_State* L) -> int
Which appeared to work and resolve the error, but then I had an issue with the last script:
lua_pushcfunction(L, addToDrawList);
I've been searching the Internet for a fix, but I can't find any.
lua_pushcfunction() takes a C-style function pointer. A capture-less lambda can be converted to such a function pointer, but a capturing lambda cannot.
Use lua_pushcclosure()1 instead. It will allow you to associate user-defined values (known as upvalues) with the C function, such as your this pointer, or just a pointer to DrawList, etc.
When a C function is created, it is possible to associate some values with it, thus creating a C closure (see §3.4); these values are then accessible to the function whenever it is called. To associate values with a C function, first these values should be pushed onto the stack (when there are multiple values, the first value is pushed first). Then lua_pushcclosure is called to create and push the C function onto the stack, with the argument n telling how many values should be associated with the function. lua_pushcclosure also pops these values from the stack.
1: lua_pushcfunction() is just a wrapper for lua_pushcclosure() with 0 upvalues defined.
For example:
auto addToDrawList = [](lua_State* L) -> int
{
const MyClassType *pThis = (const MyClassType*) lua_topointer(L, lua_upvalueindex(1));
// use pThis->DrawList as needed...
return 0;
};
...
lua_State* L = luaL_newstate();
...
//lua_pushcfunction(L, addToDrawList);
lua_pushlightuserdata(L, this);
lua_pushcclosure(L, addToDrawList, 1);
...
I'm trying to add the LUA API to my C++ program, and I'm attempting to allow the script to draw to my GUI. So far, I have this for my lambda function:
auto addToDrawList = [](lua_State* L) -> int
{
int DrawType = (int)lua_tonumber(L, -2);
std::string Label = (std::string)lua_tostring(L, -1);
bool found = false;
for (int i = 0; i <= DrawList.size(); i++)
{
if (DrawList[i].Active == false && !found)
{
switch (DrawType)
{
case(0):
break;
case(1):
DrawList[i].Active = true;
DrawList[i].DrawType = Type::TextBox;
DrawList[i].Label = Label;
break;
}
found = true;
}
}
return 0;
};
This as my LUA script being run:
const char* LUA_FILE = R"(
addToDrawList(1, "Test")
)";
This is how I'm pushing my function to the LUA stack:
lua_State* L = luaL_newstate();
lua_newtable(L);
int uiTableInd = lua_gettop(L);
lua_pushvalue(L, uiTableInd);
lua_setglobal(L, "Ui");
lua_pushcfunction(L, addToDrawList);
lua_setfield(L, -2, "addToDrawList");
The problem is within my first script, as it can't get to the 'DrawList' array as its inside of this.
So, to resolve it, I tried to add this to the lambda's capture list by doing this:
auto addToDrawList = [this](lua_State* L) -> int
Which appeared to work and resolve the error, but then I had an issue with the last script:
lua_pushcfunction(L, addToDrawList);
I've been searching the Internet for a fix, but I can't find any.
lua_pushcfunction() takes a C-style function pointer. A capture-less lambda can be converted to such a function pointer, but a capturing lambda cannot.
Use lua_pushcclosure()1 instead. It will allow you to associate user-defined values (known as upvalues) with the C function, such as your this pointer, or just a pointer to DrawList, etc.
When a C function is created, it is possible to associate some values with it, thus creating a C closure (see §3.4); these values are then accessible to the function whenever it is called. To associate values with a C function, first these values should be pushed onto the stack (when there are multiple values, the first value is pushed first). Then lua_pushcclosure is called to create and push the C function onto the stack, with the argument n telling how many values should be associated with the function. lua_pushcclosure also pops these values from the stack.
1: lua_pushcfunction() is just a wrapper for lua_pushcclosure() with 0 upvalues defined.
For example:
auto addToDrawList = [](lua_State* L) -> int
{
const MyClassType *pThis = (const MyClassType*) lua_topointer(L, lua_upvalueindex(1));
// use pThis->DrawList as needed...
return 0;
};
...
lua_State* L = luaL_newstate();
...
//lua_pushcfunction(L, addToDrawList);
lua_pushlightuserdata(L, this);
lua_pushcclosure(L, addToDrawList, 1);
...
I have the following simple code in C++ where Object is a std container:
static int create_an_object(lua_State* L) {
auto obj = static_cast<Object*>(lua_newuserdata(L, sizeof(Object*)));
*obj = another_valid_obj;
luaL_newmetatable(L, "object_metatable");
lua_pushcfunction(L, object_metatable_function);
lua_setfield(L, -2, "__index");
lua_pop(L, 1);
return 1;
}
static int object_metatable_function(lua_State* L) {
string index = luaL_checkstring(L, -1);
if (index == "foo") {
lua_pushnumber(L, 123);
}
// Handles other indices, or throws error.
}
lua_pushcfunction(L, create_an_object);
lua_setglobal(L, "create_an_object");
With the FFI above, I can achieve indexing of Object in Lua such as:
local obj = create_an_object()
print(obj.foo) -- 123
Meanwhile print(obj) shows that obj is userdata: 0x12345678.
Is it possible to use some metamethod magic so that
obj could be used as a table, while print(obj.foo) still prints 123? I am running my code in Lua 5.1.
I'm not exactly sure what you mean by "could be used as a table", but if you want to print something different from the default from print(obj), then you'll need to assign __tostring metamethod and return some string from it. This string may look like "userdata: 0x12345678 = {foo = 123}" if you want (or simply "{foo = 123}").
If you mean making it work as a table when assigning a new index to it, then __newindex metamethod should be used.
In Lua Code
Test = {}
function Test:new()
local obj = {}
setmetatable(obj, self)
self.__index = self
return obj
end
local a = Test:new()
a.ID = "abc123"
callCfunc(a)
In C Code
int callCfunc(lua_State * l)
{
void* obj = lua_topointer(l, 1); //I hope get lua's a variable
lua_pushlightuserdata(l, obj);
lua_getfield(l, 1, "ID");
std::string id = lua_tostring(l, 1); //I hoe get the value "abc123"
...
return 0;
}
But My C result is
id = null
Why? How to modify code to work fine ?
PS: I don't hope create C Test Class mapping to lua
==== update1 ====
In addition, I have added the test code to confirm correct incoming parameters.
int callCfunc(lua_State * l)
{
std::string typeName = lua_typename(l, lua_type(l, 1)); // the typeName=="table"
void* obj = lua_topointer(l, 1); //I hope get lua's a variable
lua_pushlightuserdata(l, obj);
lua_getfield(l, 1, "ID");
std::string id = lua_tostring(l, 1); //I hoe get the value "abc123"
...
return 0;
}
the result
typeName == "table"
so incoming parameter type is Correct
I found the reason
Correct c code should is ...
In C Code
int callCfunc(lua_State * l)
{
lua_getfield(l, 1, "ID");
std::string id = lua_tostring(l, -1); //-1
...
return 0;
}
Maybe this - haven't tested sorry - don't have a compiler handy
Input is the table from lua on top of the stack, so getfield(l,1, "ID") should get the field ID from the table at the top of the stack - which in this case is your input table. It then pushes the result to the top of the stack
int callCfunc(lua_State * l)
{
lua_getfield(l, 1, "ID");
std::string id = lua_tostring(l, 1); //I hoe get the value "abc123"
...
return 0;
}
In Lua Code
Test = {}
function Test:new()
local obj = {}
setmetatable(obj, self)
self.__index = self
return obj
end
local a = Test:new()
a.ID = "abc123"
callCfunc(a)
In C Code
int callCfunc(lua_State* l)
{
SetLuaState(l);
void* lua_obj = lua_topointer(l, 1); //I hope get lua's a variable
processObj(lua_obj);
...
return 0;
}
int processObj(void *lua_obj)
{
lua_State* l = GetLuaState();
lua_pushlightuserdata(l, lua_obj); //access lua table obj
int top = lua_gettop(l);
lua_getfield(l, top, "ID"); //ERROR: attempt to index a userdata value
std::string id = lua_tostring(l, -1); //I hoe get the value "abc123"
...
return 0;
}
I get the ERROR: attempt to index a userdata value
How to access lua's object from lua_topointer() ?
Storing a lua object in C, then calling it from C.
You shouldn't use lua_topointer as you can't convert it back to lua object, store your object in the registry and pass it's registry index:
int callCfunc(lua_State* L)
{
lua_pushvalue(L, 1);//push arg #1 onto the stack
int r = luaL_ref(L, LUA_REGISTRYINDEX);//stores reference to your object(and pops it from the stask)
processObj(r);
luaL_unref(L, LUA_REGISTRYINDEX, r); // removes object reference from the registry
...
int processObj(int lua_obj_ref)
{
lua_State* L = GetLuaState();
lua_rawgeti(L, LUA_REGISTRYINDEX, lua_obj_ref);//retrieves your object from registry (to the stack top)
...
You don't want to use lua_topointer for that task. In fact, the only reasonable use of lua_topointer is for debugging purposes (like logging).
As a is a table, you need to use lua_gettable to access one of its fields, or even simpler use lua_getfield. Of course you cannot pass a void* pointer to processObj for that task, but you can use the stack index instead.