How to reference 'this' in a lambda used with a LUA script - c++

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);
...

Related

How can I make an iterate function Lua C? [duplicate]

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);
...

package.preload alternative in Lua?

I learned that package.preload can be used to expose a script to other scripts.
Here's my example code.
lua_State *L = luaL_newstate();
luaL_openlibs(L);
lua_settop(L, 0);
//Script A
luaL_dostring(L, "local A = {} package.preload['A'] = function () return A end A.num = 3");
//Script B
luaL_dostring(L, "local A = require 'A' print(A.num)");
lua_close(L);
The result: 3
Although this works fine, I wonder if Script A's code can be more simplified or if there's other alternative solution to expose a script to other scripts.
ADDED: The main reason I'm asking this is because I think package.preload['A'] = function () return A end is quite long and boring to write.
In this case, where you have some set of in-C strings that represent Lua modules, package.preload is exactly the tool to use. Though your specific use of it leaves something to be desired.
Generally speaking, the modules themselves do not define their names. So hard-coding the module's name into the string is not the correct move. Similarly, modules do not register themselves; they should be registered by the environment around the module.
What you really want is to take an array of name+Lua code strings and register them as module preloads in a loop. So you'd have something like this. I'll be using Lua 5.3; you can translate it to older version of Lua pretty easily.
Also, be warned: this code is untested.
const char *lua_preloads[] =
{
"A", "local A = {}\n"
"A.num = 3\n"
"return A)\n", //Modules are usually tables, not functions.
...
NULL //Null-terminated list.
};
//Loader function
int lua_preloader_func(lua_State *L)
{
int nargs = lua_gettop(L);
int lua_func_ix = lua_upvalueindex(1);
lua_pushvalue(L, lua_func_ix);
//Move the function to the bottom of the stack
lua_insert(lua_func_ix, 1);
//Call with all of the given arguments.
lua_call(L, nargs, LUA_MULTRET);
return lua_gettop(L);
}
int top = lua_gettop(L);
//Get the package.preload table.
lua_getglobal(L, "package");
lua_getfield(L, -1, "preload");
int preload_ix = lua_gettop();
for(const char **position = lua_preloads;
*position;
position += 2)
{
const char *module_name = position[0];
const char *module = position[1];
//Compile the preload script into a Lua function.
int err = luaL_loadbufferx(L, module, strlen(module), module_name, "t");
//Check for errors in `err`.
//Create a Lua C-function with the script as an upvalue.
lua_pushcclosure(L, lua_preloader_func, 1);
//Stick that Lua C-function inside of package.preload[preload.first].
lua_setfield(L, preload_ix, module_name);
}
lua_settop(L, top);
It seems as if you want to prefix local A = {} package.preload['A'] = function () return A end to every chunk defining a module (where A is the module name). I think it would be much easier to just use string concatenation for that.
#include <string>
#include <lua.hpp>
int preload(lua_State *L, std::string const &modname,
std::string const &modcode) {
std::string code = "package.preload['" + modname + "'] = function()\n" +
"local " + modname + " = {}\n" + modcode + "\n"
"return " + modname + " end";
return luaL_dostring(L, code.c_str());
}
int main() {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
// Script A
preload(L, "A", "A.num = 3");
// Script B
luaL_dostring(L, "local A = require 'A' print(A.num)");
lua_close(L);
}

C++/Lua FFI to render userdata as a table?

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.

Array index access from metatable field/method

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

How to access lua's object from lua_topointer?

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.