I am writing in C a userdata type for use in Lua. It has some array-type properties and various methods aswell. Right now if u is of this type, I use u:set(k,v) resp. u:get(k) to access data and e.g. u:sort() as method. For this I set __index to a table containing these methods. Now if I want to access the data using u[k] = v or u[k], I need to set __newindex and __index to set resp get. But then the other methods are no longer accessible...
What's the best way to deal with this in C? I am guessing I need to write a function in C to register as __index and somehow deal with it there. Maybe check if key belongs to a Lua table of methods and if so call it.
Any help/hints would be appreciated. I did not find examples like this, although it seems a very natural thing to do (to me.)
edit: Added my C version of the solution in Lua posted in the answer below. This is more or less a direct translation, so all credit goes to #gilles-gregoire .
The following C function is registered as __index metamethod.
static int permL_index(lua_State *L) {
struct perm **pp = luaL_checkudata(L, 1, PERM_MT);
int i;
luaL_getmetatable(L, PERM_MT);
lua_pushvalue(L, 2);
lua_rawget(L, -2);
if ( lua_isnil(L, -1) ) {
/* found no method, so get value from userdata. */
i = luaL_checkint(L, 2);
luaL_argcheck(L, 1 <= i && i <= (*pp)->n, 2, "index out of range");
lua_pushinteger(L, (*pp)->v[i-1]);
};
return 1;
};
This is the code that does that,
int luaopen_perm(lua_State *L) {
luaL_newmetatable(L, PERM_MT);
luaL_setfuncs(L, permL_methods, 0);
luaL_setfuncs(L, permL_functions, 0);
lua_pop(L, 1);
luaL_newlib(L, permL_functions);
return 1;
};
where permL_methods is
static const struct luaL_Reg permL_methods[] = {
{ "__index", permL_index },
{ "__eq", permL_equal },
{ "__tostring", permL_tostring },
{ "__gc", permL_destroy },
[...]
{ NULL, NULL }
};
and permL_functions is
static const struct luaL_Reg permL_functions[] = {
{ "inverse", permL_new_inverse },
{ "product", permL_new_product },
{ "composition", permL_new_composition },
[...]
{ NULL, NULL }
};
This looks like a problem which can be solved with nested metatables. You need one metatable for the methods (like your sort() method), and a second one for index operations. That second metatable is actually the metatable of the methods metatable.
Let me write this as lua code. You need 3 tables:
-- the userdata object. I'm using a table here,
-- but it will work the same with a C userdata
u = {}
-- the "methods" metatable:
mt = {sort = function() print('sorting...') end}
-- the "operators" metatable:
op_mt = {__index = function() print('get') end}
Now, the tricky part is here: lua will first lookup u when you will call a method.
If it does not find it, it will lookup in the table pointed by the __index field of u's metatable... And Lua will repeat the process for that table!
-- first level metatable
mt.__index = mt
setmetatable(u, mt)
-- second level metatable
setmetatable(mt, op_mt)
You can now use your u like this:
> u:sort()
sorting...
> = u[1]
get
nil
EDIT: a better solution by using a function for the __index metamethod
Using a function for the __index metamethod is probably the right way to this:
u = {}
mt = {sort = function() print('sorting...') end}
setmetatable(u, mt)
mt.__index = function(t, key)
-- use rawget to avoid recursion
local mt_val = rawget(mt, key)
if mt_val ~=nil then
return mt_val
else
print('this is a get on object', t)
end
end
Usage:
> print(u)
table: 0x7fb1eb601c30
> u:sort()
sorting...
> = u[1]
this is a get on object table: 0x7fb1eb601c30
nil
>
Related
I've initialized a votes array and two functions to store the votes in our array as :
uint[2] votes = [0,0];
function vote_a() public{
votes[0] += 1;
}
function vote_b() public{
votes[1] += 1;
}
Now, I've created a "results" function which should return a string "tie", "a wins" or "b wins" on the basis of number of votes while also reassigning the number of votes to 0
function results() public returns(string memory){
uint a = votes[0];
uint b = votes[1];
votes[0]=0;
votes[1]=0;
if (a==b)
return "tie";
else if (a>b)
return "a wins";
else
return "b wins";
}
but it does not show the returned result in remix ide like a view function. And I cannot modify the state of the function to view as it'd throw an error for changing value of votes array elements. Is there any way to achieve both conditions.
This happen when you'alterating state variables defined in view functions.
Thus, view functions can only reads data from your smart contract.
To solve your issue, I tried to split the content about results() function in two functions. The first function that I called setResults() is a similar to setter functions (in other programming languages), so allows only for contract owner to handle the array values.
Second function, allows you to view the result about the comparison between a and b elements.
In the following lines, I put your smart contract adjusted where I inserted some notes:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Test {
uint[2] votes = [0,0];
address owner;
// NOTE: I set a 'owner' variable with the address value who have deployed for the first time the smart contract
constructor() {
owner = msg.sender;
}
// NOTE: Modifier that allow only for smart contract owner the access to specific function
modifier onlyOwner() {
require(msg.sender == owner, "You're not the owner!");
_;
}
function vote_a() public{
votes[0] += 1;
}
function vote_b() public{
votes[1] += 1;
}
function results() public view returns(string memory){
uint a = votes[0];
uint b = votes[1];
if (a==b)
return "tie";
else if (a>b)
return "a wins";
else
return "b wins";
}
// NOTE: I created a new function that allows you to handle the data inside array
function setResults() public onlyOwner {
votes[0] = 0;
votes[1] = 0;
}
}
IMPORTANT: Before call results() function, remember you to call setResults() for change the array values.
Say I have this metatable for a custom struct vector2_t which is inside a module mymod like this:
local mymod = {}
local ffi = require("ffi")
local C = ffi.C
ffi.cdef[[
typedef struct
{
double x;
double y;
} vector2_t;
]]
local vector2_t
vector2_t = ffi.metatype("vector2_t", {
__eq = function(lhs, rhs)
if lhs.x == rhs.x and lhs.y == rhs.y
then return true else return false end
end
-- Member functions follow...
})
vcmp.vector2 = vector2_t
-- I use this method because the script is integrated in C/C++ as a string and not
-- as a file and I need a constant name that isn't decided by the file name
package.preload["mymod"] = function(mod) return mymod end
And in another script I have this callback/event listener function which must receive a vector2_t as it's argument:
local mymod = require "mymod"
local some_pos = mymod.vector2(32, 29.7)
-- That pos argument must be an instance of mymod.vector2_t
function position_update(pos)
print("New Pos: " .. pos.x .. ", " .. pos.y .. "\n")
some_pos.x = pos.x
some_pos.y = pos.y
end
Now, I must call that callback/event listener function from C/C++ and pass an instance of that vector2_t (along with it's associated metatable) as the parameter to that function.
typedef struct
{
double x;
double y;
} vector2_t;
void call_position_update(lua_State* L, double x, double y)
{
// Retrieve the function
lua_getglobal(L, "position_update");
// Validate it
if (!lua_isfunction(L, lua_gettop(L)))
{
lua_pop(L, 1);
return;
}
// Create an instance of vector2_t
vector2_t *pos = (vector2_t *)lua_newuserdata(L, sizeof(vector2_t));
// Assign the values to the new instance
pos->x = x;
pos->y = y;
// How do I get the meta table vector2_t on to the stack?
// Reach to the module somehow...
// Get the meta table from the module
luaL_getmetatable(L, "vector2_t");
// Assign the meta table to the vector2_t instance already on the stack
lua_setmetatable(L, -2);
// I'm assuming that the vector2_t instance is already on the stack so there's nothing else to push
// Call the function with 1 argument which should be that vector2_t onto the stack
if (!lua_pcall(L, 1, 0, 0))
{
printf("Error calling function 'position_update': %s\n", lua_tostring(_Lua, -1));
}
}
I'm a bit lost and I don't know how to pas an instance of that vector2_t as the function parameter. I'm sorry for posting so much code bu I wanted to be sure that I explained correctly.
cdata and userdata are completely different things. The only interaction they have is that you can get an FFI void* pointer to userdata. Notably, there is no C API for cdata objects. Mixing them is sure to cause you a lot of headaches.
Pick either the Lua C API or the FFI, and stick to it as much as possible.
To directly answer your question:
how to pas [sic] an instance of that vector2_t as the function parameter
To a Lua C API function, it gets passed on the stack, just like other values. Note that it will be a cdata typed object, not a userdata object. Notably, you can't get a pointer to it.
How do I get the meta table vector2_t on to the stack?
You can't, since you don't make the metatable accessible to outside scripts in your first script. luaL_getmetatable only works with metatables created with luaL_newmetatable
I would like to know how I can write something like this Lua Snippet from http://lua-users.org/wiki/MetamethodsTutorial
local func_example = setmetatable({}, {__index = function (t, k)
return "key doesn't exist"
end})
local fallback_tbl = setmetatable({
foo = "bar",
[123] = 456,
}, {__index=func_example})
local fallback_example = setmetatable({}, {__index=fallback_tbl})
print(func_example[1]) --> key doesn't exist
print(fallback_example.foo) --> bar
print(fallback_example[123]) --> 456
print(fallback_example[456]) --> key doesn't exist
in the lua C api. I.e. I want lua to first check whether a key is in a members metatable and otherwise call the __index implementation. I have come up with something like this:
static const struct luaL_reg
lobj_fallback[] = {
{"__index", lobject_index},
{"__newindex", lobject_newindex},
{"__tostring", lobject_tostring},
{NULL, NULL},
};
static const struct luaL_reg
lobj_members[] = {
{"delete", lobject_delete},
{NULL, NULL}
};
{
// ....
luaL_newmetatable(L, "MyMetaTable");
luaL_register(L, NULL, lobj_members);
luaL_newmetatable(L, "MyMetaTableFallback");
luaL_register(L, NULL, lobj_fallback);
lua_setmetatable(L, -2);
// ...
}
Yet this does not work as expected, processing the fallback __index works but not the members metatable ("attempt to call method 'delete' (a nil value)").
If your intention is to fallback to the indexing function when accessing "MyMetaTable", then you have swapped the names of your table and metatable. Thus the members table (lobj_members) becomes called "MyMetaTableFallback" and the metatable (lobj_fallback) becomes called "MyMetaTable".
I'm writing a "C" userdata array structure.
As setter and getter i want normal array access (u[0] = 1 u[0]) like it's discussed here:
[c array share][1]Share Array between lua and C.
For that i need to set __index and __newindex to the set and get functions in c.
Additional i want object-oriented access, too, "like u:mymethod()". My trouble is, that i need to set now __index to the metatable itself.
Is there a way, to achieve both?
Just one of many possible ways to achieve this:
local userdata = { _array = { "A", "B", "C" } }
local mt = { }
local methods = { }
function mt.__index(userdata, k)
if methods[k] then
return methods[k]
else
return rawget(userdata, "_array")[k]
end
end
function mt.__newindex(userdata, k, v)
if methods[k] then
error "can't assign to method!"
else
rawget(userdata, "_array")[k] = v
end
end
function methods.count(userdata)
return #rawget(userdata, "_array")
end
setmetatable(userdata, mt)
userdata[3] = "Z"
print(userdata[1])
print(userdata[2])
print(userdata[3])
print(userdata:count())
userdata.count = 0
edit: As lhf pointed in his comment, it is not dangerous to use metatable as it's __index table at all, because c-methods should always check on what self they operate.
Hello I am really stumped with this seemingly simple task.
I can access the properties of a table passed to a function in C, but cannot access the members of any subtable i create in it.
Basically I want to simply be able to extract the strings from the properties table so i can create say a "wheel" according to the users expectations.
Here is what I have so far (tried so much my brain is fried)
Lua Side:
--Function
createSomething( "wheel", { canInflate = true, properties = { "large", "full" } } )
C Side:
//I can retrieve any value easily within that table, but cannot seem to extract the table
//Within it named "properties", i can access the table, but cannot extract the strings inside
if( lua_istable(L, 2) ) {
lua_getfield(L, 2, "canInflate"); // Let's extract the value for the key 'someKey'. Pushes the value on the top of the stack
static int canInflate = lua_toboolean(L, -1); // get the value of bool now at the top of stack (index: -1)
//printf("can inflate is %d\n", canInflate);
//lua_pop(L, 1); // pop the value now that we are done with it
}
//try to get the properties table
if ( lua_istable(L, 2) ) {
lua_getfield(L, 2, "properties");
const char *str = lua_tostring(L, -1);
printf( "properties 1 = %s\n", str); // NULL
lua_pop(L, 2);
}
Any help on this would be greatly appreciated
The problem you're having is with how you specify tables in Lua: the following 3 statements have exactly the same result:
t = { 'full','large'}
t = { [1] = 'full', [2] = 'large'}
t={};t[1]='full';t[2]='large'
What you want is to use the strings as keys instead of values (as is done in your code and the above samples):
t={full=true,large=true}
-- or
t={}; t.full=true; t.large=true
If you use the strings as keys your C code should work.