Implementing lua callbacks for a menu system - c++

In our menu system, we define menus in xml with lua chunks used for callbacks for menu component events. Currently, everytime a script callback is called, we call lua_loadstring which is quite slow. I'm trying to make it so this only happens once, when the menu is loaded.
My initial thought was to maintain a lua table per menu component and to do the following to add a new callback function to the table:
//create lua code that will assign a function to our table
std::string callback = "temp." + callbackName + " = function (" + params + ")" + luaCode + "end";
//push table onto stack
lua_rawgeti(L, LUA_REGISTRYINDEX, luaTableRef_);
//pop table from stack and set it as value of global "temp"
lua_setglobal(L, "temp");
//push new function onto stack
int error = luaL_loadstring(L, callback.c_str());
if ( error )
{
const char* errorMsg = lua_tostring(L, -1);
Dbg::Printf("error loading the script '%s' : %s\n", callbackName, errorMsg);
lua_pop(L,1);
return;
}
//call the lua code to insert the loaded function into the global temp table
if (lua_pcall(L, 0, 0, 0))
{
Dbg::Printf("luascript: error running the script '%s'\n", lua_tostring(L, -1));
lua_pop(L, 1);
}
//table now has function in it
This seems kind of dirty. Is there a better way that allows me to assign the function to the table directly from a lua chunk without having to use a temp global variable and running lua_pcall?

If you want to put the function in a table, then put the function in the table. It seems that your Lua-stack-fu is not strong; consider studying the manual a bit more closely.
Anyway, I'd say the biggest problem you have is your insistence on params. Callback functions should expect to be varadic; they take ... as their parameters. If they want to get those values, they should use locals like this:
local param1, param2 = ...;
But if you insist on allowing them to specify a list of parameters, you may do the following:
std::string luaChunk =
//The ; is here instead of a \n so that the line numbering
//won't be broken by the addition of this code.
"local " + params + " = ...; " +
luaCode;
lua_checkstack(L, 3);
lua_rawgeti(L, LUA_REGISTRYINDEX, luaTableRef_);
if(lua_isnil(L, -1))
{
//Create the table if it doesn't already exist.
lua_newtable(L);
//Put it in the registry.
lua_rawseti(L, LUA_REGISTRYINDEX, luaTableRef_);
//Get it back, since setting it popped it.
lua_rawgeti(L, LUA_REGISTRYINDEX, luaTableRef_);
}
//The table is on the stack. Now put the key on the stack.
lua_pushlstring(L, callbackName.c_str(), callbackName.size());
//Load up our function.
int error = luaL_loadbuffer(L, luaChunk.c_str(), luaChunk.size(),
callbackName.c_str());
if( error )
{
const char* errorMsg = lua_tostring(L, -1);
Dbg::Printf("error loading the script '%s' : %s\n", callbackName, errorMsg);
//Pop the function name and the table.
lua_pop(L, 2);
return;
}
//Put the function in the table.
lua_settable(L, -3);
//Remove the table from the stack.
lua_pop(L, 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);
...

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.

Using lua_call a lot of times in c++ function

first of all I'm sorry for my english.
My question is about how to use lua_call more than one time in C++ function. I have a program that uses lua as primary language, but it accept c++ plugins to add functionalities. I want to call a LUA function from c++, and call that c++ function in LUA Runtime.
I want to write a c++ function with a progress while is working, then pass this progress to a LUA function which is responsible to show that progress to user.
For now I've a test function in LUA:
function ShowText(text)
Dialog.Message("Hi", text);
return true;
end
and a c++ function:
static int Test(lua_State *L){
lua_pushstring(L, "Hi There");
lua_call(L, 1, 1);
lua_pushstring(L, "Again");
lua_call(L, 1, 1);
return 0;
}
Then i call this function from LUA using:
Test.Test(ShowText);
All works fine with the first lua_call but then the LUA pile is cleared, function dissapear and the second lua_call try to use the return boolean of first call instead function.
i want something like this:
static int Test(lua_State *L){
int total = 10;
for (int j; j<total; j++){
lua_pushnumber(L, j);
lua_pushnumber(L, j);
lua_call(L, 2, 1);
bool continue = IRLUA_PLUGIN_CheckBoolean(L, -1);
lua_pop(L, 1); //Delete the last boolean
if (continue == false){
break;
}
}
return 0;
}
and in LUA:
function ShowProgress(actual, final)
local percent = (actual/final)*100;
Dialog.Message("Working", "I'm in "..actual.." from "..final.." ("..percent.."%)");
return true;
end
NOTES:
Dialog.Message is a function of the program tha i'm using to show a message. Is like MessageBox(NULL, Text, Title, MB_OK); in c++.
IRLUA_PLUGIN_CheckBoolean is a function of plugin SDK that check if argument is booleand and return its value, or return an error if not.
I can do it with lua_getfield(L, LUA_GLOBALSINDEX , "FunctionName");, but is not what i want.
Someone knows how to do it?
You have understood the problem well. Here is how you fix it.
In your first example, lua_call pops the function from the stack so you need to duplicate it first. Also, the boolean returned by the function is useless, so you need to pop it or just not ask it to lua_call by setting the last argument to 0:
static int Test(lua_State *L) {
lua_pushvalue(L, 1); /* duplicate function */
lua_pushstring(L, "Hi There");
lua_call(L, 1, 0);
lua_pushstring(L, "Again");
lua_call(L, 1, 0);
return 0;
}
Now applying that to your second example:
static int Test(lua_State *L) {
int total = 10;
for (int j = 0; j<total; j++) {
lua_pushvalue(L, 1); /* duplicate function */
lua_pushnumber(L, j);
lua_pushnumber(L, total);
lua_call(L, 2, 1);
bool keep_going = IRLUA_PLUGIN_CheckBoolean(L, -1);
lua_pop(L, 1); /* pop the boolean */
if (keep_going == false) {
break;
}
}
return 0;
}
(I have fixed a few other issues with your code: the second number passed should probably be total, not j, you don't want to use continue as a variable name...)

Get lua table entry from C via integer key

I am currently using the following code to get a value from a table (cstring = const char*):
template<>
cstring luaTable::get(cstring name) {
prep_get(name); // puts table[name] at -1 in stack
cstring result;
if(!lua_isstring(L, -1)) {
report(name, "is not a string");
result = "";
}
else {
result = lua_tostring(L, -1);
}
lua_pop(L, 1);
return result;
}
void luaTable::prep_get(cstring name) {
lua_pushstring(L, name); // name at -1, table at -2
lua_gettable(L, -2);
// table[name] is now at position -1 in stack
}
This works perfectly for tables of form table = {a=10, b=2}. How can I modify it to get values from tables without keys such as table = {10, 2}?
I'm sure I'm missing something simple but can't seem to find the answer.
Thanks in advance,
Ben
Edit: added some pops
Okay sorry to answer my own question so soon - but a quick flash of inspiration lead to:
void luaTable::prep_get(cstring name) {
lua_pushstring(L, name); // name string at -1
if(lua_isnumber(L, -1)) { // call prep_get("i") for ith element etc
int key = lua_tonumber(L, -1);
lua_pop(L, 1); // remove the name string from -1
lua_pushnumber(L, key); // push name number to -1
}
lua_gettable(L, -2);
// result is now at position -1 in stack
}
which works as desired.
#user1483596 I don't think that solution would work. lua_isnumber will only return true if the value is of type number, and you just pushed a string, so it will always return false.
Instead, try something like this:
void luaTable::prep_get(cstring name) {
int num = strtol(name, 0, 0);
if (num > 0) {
lua_pushnumber(L, num);
} else {
lua_pushstring(L, name);
}
lua_gettable(L, -2);
}
Bear in mind though that it won't handle a special case. In Lua a[1] and a["1"] are different. If you use this function, you'll always treat numbers as array indices, even if they're not.
If you want to differentiate both cases, then you could overload prep_get and take a number.

Safe Lua invoke C++ registered function

Hey,everyone! I've a C++ app embedded Lua as script. A non-programmer edits the Lua script, then the C++ app invoke the Lua script and the Lua script also invokes C++ registered function.
I use Luaplus to do the above job. My question is: when the script-editor makes mistakes such as misspelling the the parameter, the C++ app crashes! What can I do to prevent this happening? thanks
Look at lua_cpcall and lua_pcall. They both allow protected function calls of lua in c. If they return a non-negative number then the call failed and the lua stack contains only the error string. In cpcalls case the stack is otherwise unmodified. For pcall you'll need to look at lua_pushcclosure to invoke a cfunction safely.
What you do is this: you create a c function with all of the lua_* calls you want, such as loadfile and dofile. You call this function using lua_cpcall or lua_pushcclosure amd lua_pcall. This allows you to detect if an error occured in t
he function you passed to cpcall.
Examples:
function hello() {
string hello_ = "Hello Lua!";
struct C {
static int call(lua_State* L) {
C *p = static_cast<C*>(lua_touserdata(L,-1));
lua_pushstring(L, p->str.c_str() );
lua_getglobal(L, "print");
lua_call(L, 1, 0); //ok
lua_pushstring(L, p->str.c_str() );
lua_getglobal(L, "notprint");
lua_call(L, 1, 0); //error -> longjmps
return 0; //Number of values on stack to 'return' to lua
}
const string& str;
} p = { hello_ };
//protected call of C::call() above
//with &p as 1st/only element on Lua stack
//any errors encountered will trigger a longjmp out of lua and
//return a non-0 error code and a string on the stack
//A return of 0 indicates success and the stack is unmodified
//to invoke LUA functions safely use the lua_pcall function
int res = lua_cpcall(L, &C::call, &p);
if( res ) {
string err = lua_tostring(L, -1);
lua_pop(L, 1);
//Error hanlder here
}
//load a .lua file
if( (res=luaL_loadfile(L, "myLuaFile.lua")) ) {
string err = lua_tostring(L, -1);
lua_pop(L, 1);
//res is one of
//LUA_ERRSYNTAX - Lua syntax error
//LUA_ERRMEM - Out of memory error
//LUE_ERRFILE - File not found/accessible error
}
//execute it
if( (res=lua_pcall(L,0,0,0)) ) {
string err = lua_tostring(L, -1);
lua_pop(L, 1);
// res is one of
// LUA_ERRRUN: a runtime error.
// LUA_ERRMEM: memory allocation error.
// LUA_ERRERR: error while running the error handler function (NULL in this case).
}
// try to call [a_int,b_str] = Foo(1,2,"3")
lua_getglobal(L,"Foo");
if( lua_isfunction(L,lua_gettop(L)) ) { //Foo exists
lua_pushnumber(L,1);
lua_pushnumber(L,2);
lua_pushstring(L,"3");
lua_pushvalue(L, -4); //copy of foo()
if( (res = lua_pcall(L, 3, 2, 0/*default error func*/)) ) {
string err = lua_tostring(L, -1);
lua_pop(L, 1);
//error: see above
}
int a_int = (int)lua_tointeger(L,-2);
string b_str = lua_tostring(L,-1);
lua_pop(L,2+1); //2 returns, + extra copy of Foo()
}
}