I'm trying to get userdata from a Lua script(chunk A) in C++(through a returned variable from function in my example) and then, later pass this userdata back to Lua script(chunk B) from C++(through a function argument in my example) so the userdata can be used in chunk B as it was in the chunk A.
MyBindings.h
class Vec2
{
public:
Vec2():x(0), y(0){};
Vec2(float x, float y):x(x), y(y){};
float x, y;
};
MyBindings.i
%module my
%{
#include "MyBindings.h"
%}
%include "MyBindings.h"
main.cpp
#include <iostream>
#include <lua.hpp>
extern "C"
{
int luaopen_my(lua_State *L);
}
int main()
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaopen_my(L);
lua_settop(L, 0);
/* chunk A */
luaL_dostring(L, "local vec2 = my.Vec2(3, 4)\n"
"function setup()\n"
"return vec2\n"
"end\n");
/* chunk B */
luaL_dostring(L, "function test(p)\n"
"print(p.x)\n"
"end\n");
void *userDataPtr = nullptr;
/* call setup function */
int top = lua_gettop(L);
lua_getglobal(L, "setup");
if (lua_pcall(L, 0, LUA_MULTRET, 0))
{
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
}
/* check the return value */
if (lua_gettop(L) - top)
{
/* store userdata to a pointer */
if (lua_isuserdata(L, -1))
userDataPtr = lua_touserdata(L, -1);
}
/* check if userDataPtr is valid */
if (userDataPtr != nullptr)
{
/* call test function */
lua_getglobal(L, "test");
lua_pushlightuserdata(L, userDataPtr); /* pass userdata as an argument */
if (lua_pcall(L, 1, 0, 0))
{
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
}
}
lua_close(L);
}
The Result I get :
[string "local vec2 = my.Vec2(3, 4)..."]:6: attempt to index a
userdata value (local 'p')
The Result I expect :
3
Is it possible to get userdata from chunk A and then pass this to chunk B so it can be used like it was in chunk A?
You're losing all information about the object's type when you get raw pointer to userdata's data and pushing it to arguments as lightuserdata. The lightuserdata even has no individual metatables.
The correct way would be to pass the Lua value as it is. Leave the original returned value on Lua stack, or copy it into other Lua container (your Lua table for temporaries, or Lua registry), then copy that value on Lua stack to pass it as an argument. That way you don't have to know anything about binding implementation. You don't even have to care if that's a userdata or any other Lua type.
Based on your code, this might look like this:
#include <iostream>
#include <lua.hpp>
extern "C"
{
int luaopen_my(lua_State *L);
}
int main()
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
/* chunk A */
luaL_dostring(L, "local vec2 = {x=3, y=4}\n"
"function setup()\n"
"return vec2\n"
"end\n");
/* chunk B */
luaL_dostring(L, "function test(p)\n"
"print(p.x)\n"
"end\n");
/* call setup function */
int top = lua_gettop(L);
lua_getglobal(L, "setup");
if (lua_pcall(L, 0, LUA_MULTRET, 0))
{
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
exit(EXIT_FAILURE); // simpy fail for demo
}
/* check the return value */
if (lua_gettop(L) - top)
{
// the top now contains the value returned from setup()
/* call test function */
lua_getglobal(L, "test");
// copy the original value as argument
lua_pushvalue(L, -2);
if (lua_pcall(L, 1, 0, 0))
{
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
exit(EXIT_FAILURE);
}
// drop the original value
lua_pop(L, 1);
}else
{
// nothing is returned, nothing to do
}
lua_close(L);
}
In addition to the other answer I would like to show a variant where you store the a reference to the value in the Lua registry. The advantage of this approach is that you don't have to keep the value on the stack and think about what the offset will be. See also 27.3.2 – References in “Programming in Lua”.
This approach uses three functions:
int luaL_ref (lua_State *L, int t);
Pops from the stack the uppermost value, stores it into the table at index t and returns the index the value has in that table. Hence to save a value in the registry we use
userDataRef = luaL_ref(L, LUA_REGISTRYINDEX);
int lua_rawgeti (lua_State *L, int index, lua_Integer n);
Pushes onto the stack the value of the element n of the table at index (t[n] in Lua). Hence to retrieve a value at index userDataRef from the registry we use
lua_rawgeti(L, LUA_REGISTRYINDEX, userDataRef);
void luaL_unref (lua_State *L, int t, int ref);
Removes the reference stored at index ref in the table at t such that the reference can be garbage collected and the index ref can be reused. Hence to remove the reference userDataRef from the registry we use
luaL_unref(L, LUA_REGISTRYINDEX, userDataRef);
#include <iostream>
#include <lua.hpp>
extern "C" {
int luaopen_my(lua_State *L);
}
int main() {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaopen_my(L);
lua_settop(L, 0);
/* chunk A */
luaL_dostring(L, "local vec2 = my.Vec2(3, 4)\n"
"function setup()\n"
"return vec2\n"
"end\n");
/* chunk B */
luaL_dostring(L, "function test(p)\n"
"print(p.x)\n"
"end\n");
int userDataRef = LUA_NOREF;
/* call setup function */
int top = lua_gettop(L);
lua_getglobal(L, "setup");
if (lua_pcall(L, 0, LUA_MULTRET, 0)) {
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
}
/* check the return value */
if (lua_gettop(L) - top) {
/* store userdata to a pointer */
userDataRef = luaL_ref(L, LUA_REGISTRYINDEX);
}
/* check if userDataRef is valid */
if (userDataRef != LUA_NOREF && userDataRef != LUA_REFNIL) {
/* call test function */
lua_getglobal(L, "test");
lua_rawgeti(L, LUA_REGISTRYINDEX, userDataRef);
/* free the registry slot (if you are done) */
luaL_unref(L, LUA_REGISTRYINDEX, userDataRef);
if (lua_pcall(L, 1, 0, 0)) {
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
}
}
lua_close(L);
}
Maybe you want to check out the Sol2 wrapper for the Lua-C-API. It can do exactly what you want with minimal boilerplate. However, it requires C++14.
#include <iostream>
#define SOL_CHECK_ARGUMENTS 1
#include <sol.hpp>
extern "C" int luaopen_my(lua_State *L);
int main() {
sol::state L;
L.open_libraries();
luaopen_my(L);
/* chunk A */
L.script("local vec2 = my.Vec2(3, 4)\n"
"function setup()\n"
"return vec2\n"
"end\n");
/* chunk B */
L.script("function test(p)\n"
"print(p.x)\n"
"end\n");
auto userDataRef = L["setup"]();
L["test"](userDataRef);
}
Related
I am sketching a small C++ program that will pass arrays to Lua and have them modified there, where I intend to have a lua script read in the program so I can modify it without needing to recompile the program
My first obstacle is to ensure Lua is able to modify arrays already allocated instead of having them allocated again in the Lua space. The data will be float and the size will be really large, but I am starting small for the moment.
To simplify this interface I tried LuaBridge 2.6, but it doesn't provide the expected result. Below is a fully "working" program.
#include <iostream>
#include <cstdint>
#include <cstring>
#include <vector>
#include <lua5.3/lua.hpp>
#include <LuaBridge/LuaBridge.h>
int main(void)
{
const uint32_t LENGTH = 512 * 256;
std::vector <float> input(LENGTH),
output(LENGTH);
memset(output.data(), 0, LENGTH * sizeof(float)); // Zero the output
for(uint32_t i = 0; i < LENGTH; i++) // Populate input
input[i] = (float)i + 0.5f;
lua_State *luastate = luaL_newstate();
luabridge::push(luastate, input.data()); // Supposedly passing a pointer to the first element of input, according to LuaBridge manual chap 3-3.1
luabridge::push(luastate, output.data()); // Same for output
luaL_dostring(luastate, "output[10] = input[256]"); // Expecting to assign this value in the C++ arrays, not in the Lua space
lua_getglobal(luastate, "output[10]"); // Find this assigned value in the Lua stack
lua_Number val = lua_tonumber(luastate, 1); // Retrieving this value from Lua to C++
std::cout << input[256] << ", " << output[10] << ", " << val << std::endl; // The values of val and in output[10] don't match
lua_close(luastate);
return 0;
}
Notice that nothing matches. What is going to output[10] in Lua is not the value of input[256] in the C++ space, but input[0].
The C++ output array is not updated from within Lua, cout shows that it remains as we initialized (0).
To confirm that, we pushed this value of output[10] to the stack, which is not input[256] in C++, and retrieved from C++.
Can you guys correct me or point me to where I should be going to achieve this?
======= UPDATE 08/11/2020 =======
To clarify what the program is doing (or supposed to do), after reading Robert's and Joseph's considerations, I post below an updated version of both the C++ part and the lua script called by it. Notice I abandoned LuaBridge since I didn't succeed in the first attempt:
C++:
#include <iostream>
#include <cstdint>
#include <cstring>
#include <vector>
#include <luajit-2.0/lua.hpp> // LuaJIT 2.0.4 from Ubuntu 16.04
int main(void)
{
const uint32_t LENGTH = 256 * 512;
std::vector <float> input(LENGTH),
output(LENGTH);
memset(output.data(), 0, LENGTH * sizeof(float));
for(uint32_t i = 0; i < LENGTH; i++)
input[i] = (float)i + 0.5f;
lua_State *luastate = luaL_newstate();
luaL_openlibs(luastate);
// Here I have to pass &input[0], &output[0] and LENGTH
// to Lua, which in turn will pass to whatever functions
// are being called from a .so lib opened in Lua-side
luaL_dofile(luastate, "my_script.lua");
lua_close(luastate);
return 0;
}
The Lua script looks like this:
local ffi = require("ffi")
local mylib = ffi.load("/path_to_lib/mylib.so")
-- Here I import and call any fuctions needed from mylib.so
-- without needing to recompile anything, just change this script
-- At this point the script has to know &input[0], &output[0] and LENGTH
ffi.cdef[[int func1(const float *in, float *out, const uint32_t LEN);]]
ffi.cdef[[int func2(const float *in, float *out, const uint32_t LEN);]]
ffi.cdef[[int funcX(const float *in, float *out, const uint32_t LEN);]]
if(mylib.func1(input, output, LENGTH) == 0) then
print("Func1 ran successfuly.")
else
print("Func1 failed.")
end
I am sketching a small C++ program that will pass arrays to Lua
The data will be float and the size will be really large,
My suggestion:
Keep the buffer on the C side (as a global variable for example)
Expose a C-function to LUA GetTableValue(Index)
Expose a C-function to Lua SetTableValue(Index, Value)
It should be something like this:
static int LUA_GetTableValue (lua_State *LuaState)
{
float Value;
/* lua_gettop returns the number of arguments */
if ((lua_gettop(LuaState) == 1) && (lua_isinteger(LuaState, -1)))
{
/* Get event string to execute (first parameter) */
Offset = lua_tointeger(LuaState, -1);
/* Get table value */
Value = LUA_FloatTable[Offset];
/* Push result to the stack */
lua_pushnumber(Value);
}
else
{
lua_pushnil(LuaState);
}
/* return 1 value */
return 1;
}
And you also need to register the function:
lua_register(LuaState, "GetTableValue", LUA_GetTableValue);
I let you write the SetTableValue but it should be very close.
Doing so, the buffer is on C side and can be accessed from Lua with dedicated functions.
I recommend you create a userdata that exposes the arrays via __index and __newindex, something like this (written as a C and C++ polyglot like Lua itself):
#include <stdio.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <lua5.3/lua.h>
#include <lua5.3/lauxlib.h>
#ifdef __cplusplus
}
#endif
struct MyNumbers {
lua_Number *arr;
lua_Integer len;
};
int MyNumbers_index(lua_State *L) {
struct MyNumbers *t = (struct MyNumbers *)luaL_checkudata(L, 1, "MyNumbers");
lua_Integer k = luaL_checkinteger(L, 2);
if(k >= 0 && k < t->len) {
lua_pushnumber(L, t->arr[k]);
} else {
lua_pushnil(L);
}
return 1;
}
int MyNumbers_newindex(lua_State *L) {
struct MyNumbers *t = (struct MyNumbers *)luaL_checkudata(L, 1, "MyNumbers");
lua_Integer k = luaL_checkinteger(L, 2);
if(k >= 0 && k < t->len) {
t->arr[k] = luaL_checknumber(L, 3);
return 0;
} else {
return luaL_argerror(L, 2,
lua_pushfstring(L, "index %d out of range", k));
}
}
struct MyNumbers *MyNumbers_new(lua_State *L, lua_Number *arr, lua_Integer len) {
struct MyNumbers *var = (struct MyNumbers *)lua_newuserdata(L, sizeof *var);
var->arr = arr;
var->len = len;
luaL_setmetatable(L, "MyNumbers");
return var;
}
int main(void) {
const lua_Integer LENGTH = 512 * 256;
lua_Number input[LENGTH], output[LENGTH];
memset(output, 0, sizeof output);
for(lua_Integer i = 0; i < LENGTH; ++i)
input[i] = i + 0.5f;
lua_State *L = luaL_newstate();
luaL_newmetatable(L, "MyNumbers");
lua_pushcfunction(L, MyNumbers_index);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, MyNumbers_newindex);
lua_setfield(L, -2, "__newindex");
/* exercise for the reader: implement __len and __pairs too, and maybe shift the indices so they're 1-based to Lua */
lua_pop(L, 1);
MyNumbers_new(L, input, LENGTH);
lua_setglobal(L, "input");
MyNumbers_new(L, output, LENGTH);
lua_setglobal(L, "output");
luaL_dostring(L, "output[10] = input[256]");
lua_getglobal(L, "output");
lua_geti(L, -1, 10);
lua_Number val = lua_tonumber(L, -1);
printf("%f, %f, %f\n", input[256], output[10], val);
lua_close(L);
}
With this approach, there is no copy of any data in Lua, and your own MyNumbers_ functions control how all access to them is done.
If you want to be able to use the arrays through LuaJIT's FFI instead of directly manipulating them in Lua, then you can pass their addresses in a light userdata instead, like this:
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <luajit-2.0/lua.h>
#include <luajit-2.0/lualib.h>
#include <luajit-2.0/lauxlib.h>
#ifdef __cplusplus
}
#endif
int main(void) {
const lua_Integer LENGTH = 256 * 512;
lua_Number input[LENGTH], output[LENGTH];
memset(output, 0, sizeof output);
for(lua_Integer i = 0; i < LENGTH; ++i)
input[i] = i + 0.5f;
lua_State *L = luaL_newstate();
luaL_openlibs(L);
lua_pushlightuserdata(L, input);
lua_setglobal(L, "input");
lua_pushlightuserdata(L, output);
lua_setglobal(L, "output");
lua_pushinteger(L, LENGTH);
lua_setglobal(L, "LENGTH");
luaL_dofile(L, "my_script.lua");
lua_close(L);
}
I'm trying to call A:update(x) and get a returned value x + 3 in C++.
Here's my code:
#include <lua.hpp>
void main()
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
lua_settop(L, 0);
luaL_dostring(L, "package.preload['A'] = function () local A = {}\n"
"function A:update(x) return x + 3 end \n"
"return A end");
//call function
lua_getglobal(L, "require");
lua_pushstring(L, "A");
if (lua_pcall(L, 1, LUA_MULTRET, 0) != 0) {
std::cerr << "lua:" << lua_tostring(L, 1) << '\n';
lua_pop(L, 1);
}
int top = lua_gettop(L);
lua_getfield(L, -1, "update");
if (!lua_isfunction(L, -1))
{
std::cerr << "lua:" << lua_tostring(L, 1) << '\n';
lua_pop(L, 1);
}
lua_pushnumber(L, 5); //pass the argument 5
if (lua_pcall(L, 1, LUA_MULTRET, 0))
{
std::cerr << "lua:" << lua_tostring(L, 1) << '\n';
lua_pop(L, 1);
}
if (lua_gettop(L) - top)
{
if (lua_isnumber(L, -1))
{
std::cout << "RETURNED : " << lua_tonumber(L, -1) << std::endl;
}
}
lua_pop(L, 1); // pop 'update'
lua_pop(L, 1); // pop 'A'
lua_close(L);
}
I expect it to print RETURNED : 8 but I get the following error:
Thread 1:EXC_BAD_ACCESS (code=1, address=0x0)
How should I correct my code to work?
EDITED: It worked as soon as I change A:update(x) to A.update(x). I thought they work identically except I can use self in a function that uses :. Could someone please explain to me why this happens?
The notation A:update(x) is syntactic sugar for A.update(A,x). That means you have to call the function update with two parameters. You are lacking the first of the two parameters.
The first parameter A is already on the stack but is located “below” the update function. Using lua_pushvalue we can push a copy of the table onto the stack.
Thus you have to call the function like this (omitting the error handling bits)
lua_getfield(L, -1, "update");
lua_pushvalue(L, -2); // push a copy of "A" onto the stack
lua_pushnumber(L, 5); //pass the argument 5
lua_pcall(L, 2, LUA_MULTRET, 0);
I created a SWIG typemap for my function.
%typemap(in) (int width, int height, lua_State *L)
{
if (!lua_isnumber(L, -1) || !lua_isnumber(L, -2))
SWIG_exception(SWIG_RuntimeError, "argument mismatch: number expected");
$1 = lua_tonumber(L, -1);
$2 = lua_tonumber(L, -2);
$3 = L;
}
But it doesn't work if I try to call the function in Lua.
I call this function like the following in Lua.
createWindow(500,300)
I just want to pass width, height from Lua to this function.
How can I fix the typemap to make it work?
Multi-argument typemaps won't do the trick here because they are designed to map a single Lua argument to multiple C++ arguments. You can help yourself with a default argument here. This is done using %typemap(default). We tell SWIG to default any lua_State *L argument to the instance used by SWIG.
%module window
%{
#include <iostream>
void createWindow(int width, int height, lua_State *L) {
std::cout << "Creating window of size " << width << "x" << height << '\n'
<< "Number of arguments: " << lua_gettop(L) << '\n';
}
%}
%typemap(default) (lua_State *L) {
$1 = L;
}
void createWindow(int width, int height, lua_State *L);
local window = require("window")
window.createWindow(500,300)
$ lua5.2 test.lua
Creating window of size 500x300
Number of arguments: 2
Despite searching hard, i couldn't find a valid Lua C API example for calling a Lua function returning custom data from C function.
For example, I have register function "GetMyVector" and then I'm calling that from lua to retrive informations from C, what I got is a table but what I want is something like access to access variable from struct like in C, for example:
local x = GetMyVector()
print(x[1]) -- i
print(x[2]) -- j
print(x[3]) -- k
-- how to access it via like this:
print(x.i)
print(x.j)
print(x.k)
my C function pushing vector in 3 dimensional array by lua_pushnumber:
static int GetMyVector(lua_State *L)
{
vec3_t vec;
vec[0] = 1;
vec[1] = 2;
vec[3] = 3;
lua_newtable(L);
lua_pushnumber(L, vec[0]);
lua_rawseti(L, -2, 1);
lua_pushnumber(L, vec[1]);
lua_rawseti(L, -2, 2);
lua_pushnumber(L, vec[2]);
lua_rawseti(L, -2, 3);
return 1;
}
You probably want lua_settable. It allows to set any keys for the table. If the key is literal you can get data by x["i"] or by x.i .
The code should be something like
static int GetMyVector(lua_State *L)
{
vec3_t vec;
vec[0] = 1;
vec[1] = 2;
vec[3] = 3;
lua_newtable(L);
lua_pushliteral(L, "i");
lua_pushnumber(L, vec[0]);
lua_settable(L, -2);
lua_pushliteral(L, "j");
lua_pushnumber(L, vec[1]);
lua_settable(L, -2);
lua_pushliteral(L, "k");
lua_pushnumber(L, vec[2]);
lua_settable(L, -2);
return 1;
}
It's a little bit extended, but thanks to #geov I have found what I was looking for, it's kinda similar to properties style like in C# i think, here you go the solution:
#define MYCLASSNAME "vec"
typedef struct {
int i, j, k;
} MyVec_t;
typedef int(*Xet_func) (lua_State *L, void *v);
/* member info for get and set handlers */
typedef const struct{
const char *name; /* member name */
Xet_func func; /* get or set function for type of member */
size_t offset; /* offset of member within MyVec_t */
} Xet_reg_pre;
typedef Xet_reg_pre * Xet_reg;
// properties
static int Get_Int(lua_State *L, void *v) {
lua_pushnumber(L, *(int*)v);
return 1;
}
static int Set_Int(lua_State *L, void *v) {
*(int*)v = luaL_checkinteger(L, 3);
return 0;
}
static int Get_Number(lua_State *L, void *v) {
lua_pushnumber(L, *(lua_Number*)v);
return 1;
}
static int Set_Number(lua_State *L, void *v) {
*(lua_Number*)v = luaL_checknumber(L, 3);
return 0;
}
static int Get_String(lua_State *L, void *v) {
lua_pushstring(L, (char*)v);
return 1;
}
static void Property_Add(lua_State *L, Xet_reg l)
{
for (; l->name; l++) {
lua_pushstring(L, l->name);
lua_pushlightuserdata(L, (void*)l);
lua_settable(L, -3);
}
}
static int Property_Call(lua_State *L)
{
Xet_reg m = (Xet_reg)lua_touserdata(L, -1);
lua_pop(L, 1);
luaL_checktype(L, 1, LUA_TUSERDATA);
return m->func(L, (void *)((char *)lua_touserdata(L, 1) + m->offset));
}
static int index_handler(lua_State *L)
{
/* stack has userdata, index */
lua_pushvalue(L, 2); /* dup index */
lua_rawget(L, lua_upvalueindex(1)); /* lookup member by name */
if (!lua_islightuserdata(L, -1)) {
lua_pop(L, 1); /* drop value */
lua_pushvalue(L, 2); /* dup index */
lua_gettable(L, lua_upvalueindex(2)); /* else try methods */
if (lua_isnil(L, -1)) /* invalid member */
luaL_error(L, "cannot get member '%s'", lua_tostring(L, 2));
return 1;
}
return Property_Call(L); /* call get function */
}
static int newindex_handler(lua_State *L)
{
/* stack has userdata, index, value */
lua_pushvalue(L, 2); /* dup index */
lua_rawget(L, lua_upvalueindex(1)); /* lookup member by name */
if (!lua_islightuserdata(L, -1)) /* invalid member */
luaL_error(L, "cannot set member '%s'", lua_tostring(L, 2));
return Property_Call(L); /* call set function */
}
static MyVec_t *CheckMyVec(lua_State *L, int index) // get data
{
MyVec_t *p;
luaL_checktype(L, index, LUA_TUSERDATA);
p = (MyVec_t *)luaL_checkudata(L, index, MYCLASSNAME);
return p;
}
static MyVec_t *PushMyVec(lua_State *L) // push data
{
MyVec_t *p = (MyVec_t *)lua_newuserdata(L, sizeof(MyVec_t));
luaL_getmetatable(L, MYCLASSNAME);
lua_setmetatable(L, -2);
return p;
}
static int MyVec_Create(lua_State *L) // C function which will push data
{
MyVec_t *p;
p = PushMyVec(L);
p->i = luaL_checkinteger(L, 1);
p->j = luaL_checkinteger(L, 2);;
p->k = luaL_checkinteger(L, 3);;
return 1;
}
static int MyVec_destroy(lua_State *L)
{
MyVec_t *p = (MyVec_t *)lua_touserdata(L, 1);
return 0;
}
static int MyVec_Position(lua_State *L)
{
MyVec_t *p = CheckMyVec(L, 1);
double x = p->i;
double y = p->j;
double z = p->k;
if (lua_gettop(L) > 1) {
p->i = luaL_checknumber(L, 2);
p->j = luaL_checknumber(L, 3);
p->k = luaL_checknumber(L, 4);
}
lua_pushnumber(L, x);
lua_pushnumber(L, y);
lua_pushnumber(L, z);
return 2;
}
static const luaL_Reg myvec_meta_methods[] = {
{ "__gc", MyVec_destroy },
{ 0, 0 }
};
static const luaL_Reg myvec_methods[] = {
{ "create", MyVec_Create },
{ "position", MyVec_Position },
{ 0, 0 }
};
static const Xet_reg_pre MyVec_get[] = {
{ "i", Get_Int, offsetof(MyVec_t, i) },
{ "j", Get_Int, offsetof(MyVec_t, j) },
{ "k", Get_Int, offsetof(MyVec_t, k) },
{ 0, 0 }
};
static const Xet_reg_pre MyVec_set[] = {
{ "i", Set_Int, offsetof(MyVec_t, i) },
{ "j", Set_Int, offsetof(MyVec_t, j) },
{ "k", Set_Int, offsetof(MyVec_t, k) },
{ 0, 0 }
};
int MyVec_Register(lua_State *L)
{
int metatable, methods;
/* create methods table, & add it to the table of globals */
luaL_openlib(L, MYCLASSNAME, myvec_methods, 0);
methods = lua_gettop(L);
/* create metatable for MyVec_t, & add it to the registry */
luaL_newmetatable(L, MYCLASSNAME);
luaL_openlib(L, 0, myvec_meta_methods, 0); /* fill metatable */
metatable = lua_gettop(L);
lua_pushliteral(L, "__metatable");
lua_pushvalue(L, methods); /* dup methods table*/
lua_rawset(L, metatable); /* hide metatable:
metatable.__metatable = methods */
lua_pushliteral(L, "__index");
lua_pushvalue(L, metatable); /* upvalue index 1 */
Property_Add(L, MyVec_get); /* fill metatable with getters */
lua_pushvalue(L, methods); /* upvalue index 2 */
lua_pushcclosure(L, index_handler, 2);
lua_rawset(L, metatable); /* metatable.__index = index_handler */
lua_pushliteral(L, "__newindex");
lua_newtable(L); /* table for members you can set */
Property_Add(L, MyVec_set); /* fill with setters */
lua_pushcclosure(L, newindex_handler, 1);
lua_rawset(L, metatable); /* metatable.__newindex = newindex_handler */
lua_pop(L, 1); /* drop metatable */
return 1; /* return methods on the stack */
}
edit:
As I commented before, my problem is I can't create a table in C which uses strings as keys. I made a quick test program which demonstrates the problem I'm having:
Here is the C++ part of it:
#include <lua.hpp>
#include <String.h>
BString
LuaTypeToString(lua_State *L, int index, int type)
{
BString out;
switch (type)
{
case LUA_TSTRING:
{
out << "'" << lua_tostring(L, index) << "'";
break;
}
case LUA_TBOOLEAN:
{
out << (lua_toboolean(L, index) ? "true" : "false");
break;
}
case LUA_TNUMBER:
{
out << (float)lua_tonumber(L, index);
break;
}
default:
{
out << lua_typename(L, type);
break;
}
}
return out;
}
void
DumpLuaTable(lua_State *L, int tableIndex)
{
lua_pushnil(L);
printf("\t{ ");
while (lua_next(L, tableIndex) != 0)
{
BString keyString = lua_tostring(L, -2);
BString valueString;
int type = lua_type(L, -1);
if (type == LUA_TTABLE)
DumpLuaTable(L, lua_gettop(L));
else
valueString = LuaTypeToString(L, -1, type);
printf("%s=%s,",
keyString.String(),
valueString.String());
lua_pop(L, 1);
if (lua_isnumber(L, -1))
{
lua_pop(L, 1);
break;
}
}
printf(" }");
}
void
DumpLuaStack(lua_State *L)
{
printf("DumpLuaStack:\n");
int top = lua_gettop(L);
for (int i = 1; i <= top; i++)
{
int type = lua_type(L, i);
if (type == LUA_TTABLE)
DumpLuaTable(L, i);
else
printf("\t%s ", LuaTypeToString(L, i, type).String());
}
printf("\n");
}
static
int
ReturnTable(lua_State *L)
{
lua_newtable(L);
lua_pushnumber(L, 3.14);
lua_pushstring(L, "1");
lua_settable(L, -3);
DumpLuaStack(L);
return 1;
}
int
main(void)
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
lua_pushcfunction(L, ReturnTable);
lua_setglobal(L, "ReturnTable");
int status = luaL_dofile(L, "luatest.lua");
if (status)
printf("Status = %d: %s\n", status, lua_tostring(L, -1));
lua_close(L);
return status;
}
And the Lua part of it:
function DumpTable(table)
print("LuaDumpTable")
local out = "\t"
if #table > 0 then
for i, v in ipairs(table) do
out = out .. i .. "=" .. v .. " "
end
print(out)
else
print("\tEmpty table")
end
end
value = ReturnTable()
if (type(value) == "table") then
DumpTable(value)
else
print(type(value))
end
The code as it is works without problem -- the Lua script prints out what you would expect. Change the line which pushes 3.14 to the stack to, say, lua_pushstring(L, "foo"); and all I get is an empty table on the Lua side. Any ideas what I'm doing wrong here?
You're checking the table incorrectly in Lua:
if #table > 0 then
The #table is just telling you that the first unused integer in the table is 0. When you have a string inserted into the table, it doesn't increment the count since it's not part of a continuous array, and you also can't process it with ipairs. Just switch to using pairs and you should see foo:
function DumpTable(table)
print("LuaDumpTable")
local out = "\t"
for i, v in pairs(table) do
out = out .. i .. "=" .. v .. " "
end
print(out)
end
Older note, left as an fyi:
I doubt this would cause the issue, but for API's being called from Lua, you typically want to pop the input off of the stack. So after the lua_tonumber call:
int32 value = lua_tonumber(L, 1);
lua_pop(L, 1);
rgb_color c = ui_color((color_which)value);