Skip to content

Commit

Permalink
Create the Lua metatable in C++.
Browse files Browse the repository at this point in the history
This is only done for types that are wrapped via the LuaValue class.

- Removes the creation of the metatable in autoexec.lua (as it's done in C++ now).
- Removes generating the global functions that were used by the Lua-generated metatable. Instead:
- Generate separate containers with wrapper functions to be used in LuaValue: a simple array for normal class functions, two maps that map the member name to the setter/getter function.

LuaValue::get_metatable now generates the metatable on its own (or simply returns the existing one).

To make every object in Lua consistent, all the push-object-on-stack code uses LuaValue/LuaReference to push values (including setting the metatable).

Use the Lua function tripoint_mt_test to test it. The serialize function was added to have a by-value object with a member function (not only member access).
  • Loading branch information
BevapDin committed May 23, 2015
1 parent fa3ec2e commit 44501d7
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 134 deletions.
19 changes: 19 additions & 0 deletions data/json/preload.lua
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,25 @@ function custom_flumed(item, active)
end
end

-- Test the tripoint metatable: accessing functions, reading and writing members.
function tripoint_mt_test()
-- TODO: make tripoint.new()... or even tripoint(...) work properly
-- For now, this is the way to create a new tripoint.
trp = player:pos()
-- Function call hard coded in catabindings.cpp
local a = trp:serialize()
-- You may try this (it should fail, Lua should complain, but it should *not* crash):
-- trp:non_existing_function()
-- trp.non_existing_member = 10
game.popup("a: " .. a) -- Should be a json string [9, 3, 1]
-- Read a member, this is dispatched via the READ_MEMBERS in C++
local b = trp.x
game.popup("a: " .. a .. ", b: " .. b)
-- Write a member, this is dispatched via the WRITE_MEMBERS in C++
trp.x = 1000;
game.popup("b: " .. b .. ", trp.x: " .. trp.x) -- b should be the same as before
end

game.register_iuse("HICCUP", hiccup)
game.register_iuse("TELLSTUFF", tellstuff)
game.register_iuse("CUSTOM_PROZAC", custom_prozac)
Expand Down
65 changes: 0 additions & 65 deletions lua/autoexec.lua
Original file line number Diff line number Diff line change
@@ -1,70 +1,5 @@
--dofile("./class_definitions.lua")

function generate_metatable(name)
return {
__index = function(userdata, key)
-- iterate over the class inheritance hierarchy
local current_name = name
while current_name do
local class = classes[current_name]
local attribute = class.attributes[key]
if attribute then
return game[name.."_get_"..key](userdata)
elseif class.functions[key] then
return game[name.."_"..key]
else
current_name = class.parent
end
end
error("Unknown "..name.." attribute: "..key)
end,

__newindex = function(userdata, key, value)
-- iterate over the class inheritance hierarchy
local current_name = name
while current_name do
local class = classes[current_name]
local attribute = class.attributes[key]
if attribute then
if not attribute.writable then
error("Attempting to set read-only item attribute: "..key)
end

-- convert our generic type from the wrapper definition to an
-- actual lua type
local attribute_type = attribute.type
if attribute_type == "int" then
attribute_type = "number"
elseif attribute_type == "bool" then
attribute_type = "boolean"
elseif attribute_type == "string" then
attribute_type = "string"
else
-- otherwise it's probably a wrapped class,
-- so we expect a userdata
attribute_type = "userdata"
end

if type(value) ~= attribute_type then
error("Invalid value for "..name.."."..key..": "..tostring(value), 2)
end

return game[name.."_set_"..key](userdata, value)
else
current_name = class.parent
end
end
error("Unknown "..name.." attribute: "..key)
end,

__gc = game.__gc
}
end

for key, _ in pairs(classes) do
_G[key.."_metatable"] = generate_metatable(key)
end

outdated_metatable = {
__index = function(userdata, key)
error("Attempt to access outdated gamedata.")
Expand Down
5 changes: 4 additions & 1 deletion lua/class_definitions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,10 @@ classes = {
}
},
functions = {
serialize = {
args = {},
rval = "string"
}
}
},
uimenu = {
Expand Down Expand Up @@ -595,7 +599,6 @@ classes = {
attributes = {
},
functions = {
i_at = { rval = "map_stack", args = { "tripoint" } },
save = { rval = nil, args = { } },
load = { rval = nil, args = { "int", "int", "int", "bool" } },
shift = { rval = nil, args = { "int", "int" } },
Expand Down
138 changes: 96 additions & 42 deletions src/catalua.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,31 +108,6 @@ bool lua_report_error(lua_State *L, int err, const char *path) {
return true;
}

void update_globals(lua_State *L)
{
// Make sure the player reference is up to date.
{
player **player_userdata = (player **) lua_newuserdata(L, sizeof(player *));
*player_userdata = &g->u;

// Set the metatable for the player.
luah_setmetatable(L, "player_metatable");

luah_setglobal(L, "player", -1);
}

// Make sure the map reference is up to date.
{
map **map_userdata = (map **) lua_newuserdata(L, sizeof(map *));
*map_userdata = &g->m;

// Set the metatable for the player.
luah_setmetatable(L, "map_metatable");

luah_setglobal(L, "map", -1);
}
}

/**
* A value, copied into Luas own memory.
*/
Expand All @@ -141,6 +116,14 @@ class LuaValue {
private:
/** Defined by generate_bindings.lua in catabindings.cpp */
static const char * const METATABLE_NAME;
/** Defined by generate_bindings.lua in catabindings.cpp */
static const luaL_Reg FUNCTIONS[];
/** Defined by generate_bindings.lua in catabindings.cpp */
using MRMap = std::map<std::string, int(*)(lua_State*)>;
static const MRMap READ_MEMBERS;
/** Defined by generate_bindings.lua in catabindings.cpp */
using MWMap = std::map<std::string, int(*)(lua_State*)>;
static const MWMap WRITE_MEMBERS;

static int gc( lua_State* const L )
{
Expand All @@ -150,20 +133,86 @@ class LuaValue {
return 0;
}
/**
* This loads the metatable and adds the __gc entry so the C++ data is properly destroyed
* (invoking the destructor) when Lua de-allocates its memory.
* The function leaves the metatable on the stack!
* Wrapper for the Lua __index entry in the metatable of the userdata.
* It queries the actual metatable in case the call goes to a function (and does not request
* and actual class member) and returns that function (if found).
* If there is no function of the requested name, it looks up the name in @ref READ_MEMBERS,
* if it's there, it calls the function that the entry refers to (which acts as a getter).
* Finally it returns nil, which is what Lua would have used anyway.
*/
static int index( lua_State * const L )
{
// -2 is the userdata, -1 is the key (funtion to call)
const char * const key = lua_tostring( L, -1 );
if( key == nullptr ) {
luaL_error( L, "Invalid input to __index: key is not a string." );
}
if( luaL_getmetafield( L, -2, key ) != 0 ) {
// There is an entry of that name, return it.
lua_remove( L, -3 ); // remove userdata
lua_remove( L, -2 ); // remove key
// -1 is now the things we have gotten from luaL_getmetafield, return it.
return 1;
}
const auto iter = READ_MEMBERS.find( key );
if( iter == READ_MEMBERS.end() ) {
// No such member or function
lua_pushnil( L );
return 1;
}
lua_remove( L, -1 ); // remove key
// userdata is still there (now on -1, where it is expected by the getter)
return iter->second( L );
}
/**
* Wrapper for the Lua __newindex entry in the metatable of the userdata.
* It looks up the name of the requested member in @ref WRITE_MEMBERS and (if found),
* calls the function that the entry refers to (which acts as a setter).
*/
static int newindex( lua_State * const L )
{
// -3 is the userdata, -2 is the key (name of the member), -1 is the value
const char * const key = lua_tostring( L, -2 );
if( key == nullptr ) {
luaL_error( L, "Invalid input to __newindex: key is not a string." );
}
const auto iter = WRITE_MEMBERS.find( key );
if( iter == WRITE_MEMBERS.end() ) {
luaL_error( L, "Unknown attribute" );
}
lua_remove( L, -2 ); // key, userdata is still there, but now on -2, and the value is on -1
return iter->second( L );
}
/**
* This loads the metatable (and adds the available functions) and pushes it on the stack.
*/
static void get_metatable( lua_State* const L )
{
lua_getglobal( L, METATABLE_NAME );
// Create table (if it does not already exist), pushes it on the stack.
// If the table already exists, we have already filled it, so we can return
// without doing it again.
if( luaL_newmetatable( L, METATABLE_NAME ) == 0 ) {
return;
}
// Push the metatable itself, the stack now contains two pointers to the same metatable
lua_pushvalue( L, -1 );
// Set __index in the new metatable (-2 on the stack) to be the metatable itself.
// It also pops one value from the stack.
lua_setfield( L, -2, "__index" );
// Now set the actual functions of the metatable.
luaL_setfuncs( L, &FUNCTIONS[0], 0 );

// Calling the destructor is really only needed when it's not trivial (e.g. pointers)
if( !std::is_trivially_destructible<T>::value ) {
// Push function pointer
lua_pushcfunction( L, &gc );
// -1 would be the function pointer, -2 is the metatable, the function pointer is popped
lua_setfield( L, -2, "__gc" );
}
lua_pushcfunction( L, &index );
lua_setfield( L, -2, "__index" );
lua_pushcfunction( L, &newindex );
lua_setfield( L, -2, "__newindex" );
}

public:
Expand Down Expand Up @@ -232,6 +281,15 @@ class LuaReference : private LuaValue<T*> {
}
};

void update_globals(lua_State *L)
{
LuaReference<player>::push( L, g->u );
luah_setglobal( L, "player", -1 );

LuaReference<map>::push( L, g->m );
luah_setglobal( L, "map", -1 );
}

// iuse abstraction to make iuse's both in lua and C++ possible
// ------------------------------------------------------------
void Item_factory::register_iuse_lua(const std::string &name, int lua_function)
Expand Down Expand Up @@ -266,12 +324,8 @@ int lua_mapgen(map *m, std::string terrain_type, mapgendata, int t, float, const
return 0;
}
lua_State *L = lua_state;
{
map **map_userdata = (map **) lua_newuserdata(L, sizeof(map *));
*map_userdata = m;
luah_setmetatable(L, "map_metatable");
luah_setglobal(L, "map", -1);
}
LuaReference<map>::push( L, m );
luah_setglobal(L, "map", -1);

int err = luaL_loadstring(L, scr.c_str() );
if( lua_report_error( L, err, scr.c_str() ) ) {
Expand Down Expand Up @@ -371,9 +425,7 @@ static int game_monster_type(lua_State *L)
{
const std::string parameter1 = lua_tostring_wrapper( L, 1 );

mtype **monster_type = (mtype **) lua_newuserdata(L, sizeof(mtype *));
*monster_type = GetMType(parameter1);
luah_setmetatable(L, "mtype_metatable");
LuaReference<mtype>::push( L, GetMType( parameter1 ) );

return 1; // 1 return values

Expand Down Expand Up @@ -409,6 +461,7 @@ static int game_items_at(lua_State *L)
lua_pushnumber(L, i++ + 1);
item **item_userdata = (item **) lua_newuserdata(L, sizeof(item *));
*item_userdata = &an_item;
// TODO: update using LuaReference<item>
luah_setmetatable(L, "item_metatable");
lua_rawset(L, -3);
}
Expand Down Expand Up @@ -472,10 +525,11 @@ static int game_monster_at(lua_State *L)
int parameter3 = (int) lua_tonumber(L, 3);
int monster_idx = g->mon_at( {parameter1, parameter2, parameter3} );

monster &mon_ref = g->zombie(monster_idx);
monster **monster_userdata = (monster **) lua_newuserdata(L, sizeof(monster *));
*monster_userdata = &mon_ref;
luah_setmetatable(L, "monster_metatable");
if( monster_idx < 0 ) {
LuaReference<monster>::push( L, nullptr );
} else {
LuaReference<monster>::push( L, g->zombie( monster_idx ) );
}

return 1; // 1 return values
}
Expand Down
Loading

0 comments on commit 44501d7

Please sign in to comment.