From 44501d764fe900bad8818590873e6f4046e96303 Mon Sep 17 00:00:00 2001 From: BevapDin Date: Sat, 23 May 2015 16:13:31 +0200 Subject: [PATCH] Create the Lua metatable in C++. 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). --- data/json/preload.lua | 19 +++++ lua/autoexec.lua | 65 ---------------- lua/class_definitions.lua | 5 +- src/catalua.cpp | 138 +++++++++++++++++++++++----------- src/lua/generate_bindings.lua | 79 ++++++++++++------- 5 files changed, 172 insertions(+), 134 deletions(-) diff --git a/data/json/preload.lua b/data/json/preload.lua index 6871d25b696df..ddcd4752f85a8 100644 --- a/data/json/preload.lua +++ b/data/json/preload.lua @@ -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) diff --git a/lua/autoexec.lua b/lua/autoexec.lua index 93ac72e2aabed..f70110b8bf81d 100644 --- a/lua/autoexec.lua +++ b/lua/autoexec.lua @@ -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.") diff --git a/lua/class_definitions.lua b/lua/class_definitions.lua index ed11edac17b87..1986b012a2204 100644 --- a/lua/class_definitions.lua +++ b/lua/class_definitions.lua @@ -563,6 +563,10 @@ classes = { } }, functions = { + serialize = { + args = {}, + rval = "string" + } } }, uimenu = { @@ -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" } }, diff --git a/src/catalua.cpp b/src/catalua.cpp index 7ef0e57ac71c5..b8a7dae8aa0e2 100644 --- a/src/catalua.cpp +++ b/src/catalua.cpp @@ -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. */ @@ -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; + static const MRMap READ_MEMBERS; + /** Defined by generate_bindings.lua in catabindings.cpp */ + using MWMap = std::map; + static const MWMap WRITE_MEMBERS; static int gc( lua_State* const L ) { @@ -150,13 +133,75 @@ 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::value ) { // Push function pointer @@ -164,6 +209,10 @@ class LuaValue { // -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: @@ -232,6 +281,15 @@ class LuaReference : private LuaValue { } }; +void update_globals(lua_State *L) +{ + LuaReference::push( L, g->u ); + luah_setglobal( L, "player", -1 ); + + LuaReference::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) @@ -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::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() ) ) { @@ -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::push( L, GetMType( parameter1 ) ); return 1; // 1 return values @@ -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 luah_setmetatable(L, "item_metatable"); lua_rawset(L, -3); } @@ -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::push( L, nullptr ); + } else { + LuaReference::push( L, g->zombie( monster_idx ) ); + } return 1; // 1 return values } diff --git a/src/lua/generate_bindings.lua b/src/lua/generate_bindings.lua index a6572aa93f6be..342387a0c9a32 100644 --- a/src/lua/generate_bindings.lua +++ b/src/lua/generate_bindings.lua @@ -262,6 +262,55 @@ for name, func in pairs(global_functions) do cpp_output = cpp_output .. generate_global_function_wrapper(name, func.cpp_name, func.args, func.rval) end +-- luaL_Reg is the name of the struct in C which this creates and returns. +function luaL_Reg(name, suffix) + local cpp_name = name .. "_" .. suffix + local lua_name = suffix + return tab .. '{"' .. lua_name .. '", ' .. cpp_name .. '},' .. br +end +-- Creates the LuaValue::FUNCTIONS array, containing all the public functions of the class. +function generate_functions_static(cpp_type, class, name) + cpp_output = cpp_output .. "template<>" .. br + cpp_output = cpp_output .. "const luaL_Reg " .. cpp_type .. "::FUNCTIONS[] = {" .. br + while class do + for key, _ in pairs(class.functions) do + cpp_output = cpp_output .. luaL_Reg(name, key) + end + class = classes[class.parent] + end + cpp_output = cpp_output .. tab .. "{NULL, NULL}" .. br -- sentinel to indicate end of array + cpp_output = cpp_output .. "};" .. br +end +-- Creates the LuaValue::READ_MEMBERS map, containing the getters. Example: +-- const LuaValue::MRMap LuaValue::READ_MEMBERS = { { "id", foo_get_id }, ... }; +function generate_read_members_static(cpp_type, class, name) + cpp_output = cpp_output .. "template<>" .. br + cpp_output = cpp_output .. "const " .. cpp_type .. "::MRMap " .. cpp_type .. "::READ_MEMBERS = {" .. br + while class do + for key, attribute in pairs(class.attributes) do + cpp_output = cpp_output .. tab .. "{\"" .. key .. "\", " .. name .. "_get_" .. key .. "}," .. br + end + class = classes[class.parent] + end + cpp_output = cpp_output .. "};" .. br +end +-- Creates the LuaValue::READ_MEMBERS map, containing the setters. Example: +-- const LuaValue::MWMap LuaValue::WRITE_MEMBERS = { { "id", foo_set_id }, ... }; +function generate_write_members_static(cpp_type, class, name) + cpp_output = cpp_output .. "template<>" .. br + cpp_output = cpp_output .. "const " .. cpp_type .. "::MWMap " .. cpp_type .. "::WRITE_MEMBERS = {" .. br + while class do + for key, attribute in pairs(class.attributes) do + if attribute.writable then + cpp_output = cpp_output .. tab .. "{\"" .. key .. "\", " .. name .. "_set_" .. key .. "}," .. br + end + end + class = classes[class.parent] + end + cpp_output = cpp_output .. "};" .. br +end + +-- Create the static constant members of LuaValue for name, value in pairs(classes) do cpp_output = cpp_output .. "template<>" .. br local cpp_name = "" @@ -273,36 +322,14 @@ for name, value in pairs(classes) do cpp_name = "LuaValue<" .. name .. "*>" end cpp_output = cpp_output .. "const char * const " .. cpp_name .. "::METATABLE_NAME = \"" .. name .. "_metatable\";" .. br + generate_functions_static(cpp_name, value, name) + generate_read_members_static(cpp_name, value, name) + generate_write_members_static(cpp_name, value, name) end --- Create a lua registry with our getters and setters. +-- Create a lua registry with the global functions cpp_output = cpp_output .. "static const struct luaL_Reg gamelib [] = {"..br --- Now that the wrapper functions are implemented, we need to make them accessible to lua. --- For this, the lua "registry" is used, which maps strings to C functions. -function generate_registry(class, name) - for key, attribute in pairs(class.attributes) do - local getter_name = name.."_get_"..key - local setter_name = name.."_set_"..key - cpp_output = cpp_output .. tab .. '{"'..getter_name..'", '..getter_name..'},'..br - if attribute.writable then - cpp_output = cpp_output .. tab .. '{"'..setter_name..'", '..setter_name..'},'..br - end - end - - for key, func in pairs(class.functions) do - local func_name = name.."_"..key - cpp_output = cpp_output .. tab .. '{"'..func_name..'", '..func_name..'},'..br - end -end - -for name, value in pairs(classes) do - while value do - generate_registry(value, name) - value = classes[value.parent] - end -end - for name, func in pairs(global_functions) do cpp_output = cpp_output .. tab .. '{"'..name..'", '..name..'},'..br end