From bc74c5984e8ccfd1ed0d0ba4f1e058fce7acc46a Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 27 Mar 2012 21:47:52 +0400 Subject: [PATCH] Implement recursive transfer of values from lua to c++ structures. E.g. df.global.cursor = { x = 1, y = 2, z = 3 }. The lua data must be represented by raw lua tables. For structs, the entries in the table are assigned to matching fields. For containers, if a 'resize' field is missing or nil, the table is treated like 1-based lua array, and the container is resized to match its # length. Otherwise, the field must be either an explicit number, true or false. If it is true, the size is selected by the highest index in the table. After that, entries are copied using 0-based indices. For pointers, the table must match the target object. If the pointer is null, the object is auto-allocated; this can be controlled using the 'new' field, the value of which will be passed to df.new(). --- library/LuaTypes.cpp | 70 ++++++++++++++++++++- library/LuaWrapper.cpp | 115 ++++++++++++++++++++++++++++++++--- library/include/LuaWrapper.h | 7 +++ 3 files changed, 181 insertions(+), 11 deletions(-) diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 536bcd72a..1e33ed713 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -65,9 +65,22 @@ void constructed_identity::lua_read(lua_State *state, int fname_idx, void *ptr) push_object_internal(state, this, ptr); } +static void invoke_assign(lua_State *state, type_identity *id, void *ptr, int val_index) +{ + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_ASSIGN_NAME); + push_object_internal(state, id, ptr); + lua_pushvalue(state, val_index); + lua_call(state, 2, 0); +} + void constructed_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) { - field_error(state, fname_idx, "complex object", "write"); + if (lua_istable(state, val_index)) + { + invoke_assign(state, this, ptr, val_index); + } + else + field_error(state, fname_idx, "complex object", "write"); } void enum_identity::lua_read(lua_State *state, int fname_idx, void *ptr) @@ -150,6 +163,47 @@ void df::pointer_identity::lua_read(lua_State *state, int fname_idx, void *ptr) lua_read(state, fname_idx, ptr, target); } +static void autovivify_ptr(lua_State *state, int fname_idx, void **pptr, + type_identity *target, int val_index) +{ + lua_getfield(state, val_index, "new"); + + if (!lua_isnil(state, -1)) + { + int top = lua_gettop(state); + + type_identity *suggested = get_object_identity(state, top, "autovivify", true, true); + + if (!is_type_compatible(state, target, 0, suggested, top+1, false)) + field_error(state, fname_idx, "incompatible suggested autovivify type", "write"); + + lua_pop(state, 1); + + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_NEW_NAME); + lua_swap(state); + lua_call(state, 1, 1); + + void *nval = get_object_internal(state, target, top, false); + + if (!nval) + field_error(state, fname_idx, "inconsistent autovivify type", "write"); + + *pptr = nval; + } + else + { + if (!target) + field_error(state, fname_idx, "trying to autovivify void*", "write"); + + *pptr = target->allocate(); + + if (!*pptr) + field_error(state, fname_idx, "could not allocate in autovivify", "write"); + } + + lua_pop(state, 1); +} + void df::pointer_identity::lua_write(lua_State *state, int fname_idx, void *ptr, type_identity *target, int val_index) { @@ -157,6 +211,13 @@ void df::pointer_identity::lua_write(lua_State *state, int fname_idx, void *ptr, if (lua_isnil(state, val_index)) *pptr = NULL; + else if (lua_istable(state, val_index)) + { + if (!*pptr) + autovivify_ptr(state, fname_idx, pptr, target, val_index); + + invoke_assign(state, target, *pptr, val_index); + } else { void *nval = get_object_internal(state, target, val_index, false); @@ -435,10 +496,15 @@ static void write_field(lua_State *state, const struct_field_info *field, void * case struct_field_info::POINTER: df::pointer_identity::lua_write(state, 2, ptr, field->type, value_idx); + return; case struct_field_info::STATIC_ARRAY: case struct_field_info::STL_VECTOR_PTR: - field_error(state, 2, "complex object", "write"); + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_ASSIGN_NAME); + read_field(state, field, ptr); + lua_pushvalue(state, value_idx); + lua_call(state, 2, 0); + return; case struct_field_info::END: return; diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 4758f04f1..11db14875 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -255,8 +255,8 @@ static void fetch_container_details(lua_State *state, int meta, type_identity ** /** * Check if type1 and type2 are compatible, possibly using additional metatable data. */ -static bool is_type_compatible(lua_State *state, type_identity *type1, int meta1, - type_identity *type2, int meta2, bool exact_equal) +bool LuaWrapper::is_type_compatible(lua_State *state, type_identity *type1, int meta1, + type_identity *type2, int meta2, bool exact_equal) { if (type1 == type2) return true; @@ -417,9 +417,9 @@ static bool is_valid_metatable(lua_State *state, int objidx, int metaidx) /** * Given a DF object reference or type, safely retrieve its identity pointer. */ -static type_identity *get_object_identity(lua_State *state, int objidx, - const char *ctx, bool allow_type = false, - bool keep_metatable = false) +type_identity *LuaWrapper::get_object_identity(lua_State *state, int objidx, + const char *ctx, bool allow_type, + bool keep_metatable) { if (!lua_getmetatable(state, objidx)) luaL_error(state, "Invalid object in %s", ctx); @@ -608,6 +608,31 @@ static int meta_new(lua_State *state) return 1; } +static void invoke_resize(lua_State *state, int table, lua_Integer size) +{ + lua_getfield(state, table, "resize"); + lua_pushvalue(state, table); + lua_pushinteger(state, size); + lua_call(state, 2, 0); +} + +static void copy_table(lua_State *state, int dest, int src, int skipkey) +{ + lua_pushnil(state); + + while (lua_next(state, src)) + { + if (lua_equal(state, -2, skipkey)) + lua_pop(state, 1); + else + { + lua_pushvalue(state, -2); + lua_swap(state); + lua_settable(state, dest); + } + } +} + /** * Method: assign data between objects. */ @@ -618,11 +643,83 @@ static int meta_assign(lua_State *state) if (argc != 2) luaL_error(state, "Usage: target:assign(src) or df.assign(target,src)"); - type_identity *id1, *id2; - check_type_compatible(state, 1, 2, &id1, &id2, "df.assign()", false, false); + if (!lua_istable(state, 2)) + { + type_identity *id1, *id2; + check_type_compatible(state, 1, 2, &id1, &id2, "df.assign()", false, false); + + if (!id1->copy(get_object_ref(state, 1), get_object_ref(state, 2))) + luaL_error(state, "No copy support for %s", id1->getFullName().c_str()); + } + else + { + type_identity *id = get_object_identity(state, 1, "df.assign()", false); + + if (id->isContainer()) + { + lua_pushstring(state, "resize"); + int resize_str = lua_gettop(state); - if (!id1->copy(get_object_ref(state, 1), get_object_ref(state, 2))) - luaL_error(state, "No copy support for %s", id1->getFullName().c_str()); + lua_dup(state); + lua_rawget(state, 2); + + if (lua_isnil(state,-1)) + { + /* + * nil or missing resize field => 1-based lua array + */ + int size = lua_objlen(state, 2); + + lua_pop(state, 1); + invoke_resize(state, 1, size); + + for (int i = 1; i <= size; i++) + { + lua_pushinteger(state, i-1); + lua_rawgeti(state, 2, i); + lua_settable(state, 1); + } + } + else + { + if (lua_isboolean(state, -1)) + { + // resize=false => just assign + // resize=true => find the largest index + if (lua_toboolean(state, -1)) + { + lua_Integer size = 0; + + lua_pushnil(state); + while (lua_next(state, 2)) + { + lua_pop(state, 1); + if (lua_isnumber(state,-1)) + size = std::max(size, lua_tointeger(state,-1)+1); + } + + invoke_resize(state, 1, size); + } + } + else + { + // otherwise, must be an explicit number + if (!lua_isnumber(state,-1)) + luaL_error(state, "Invalid container.resize value in df.assign()"); + + invoke_resize(state, 1, lua_tointeger(state, -1)); + } + + lua_pop(state, 1); + copy_table(state, 1, 2, resize_str); + } + } + else + { + lua_pushstring(state, "new"); + copy_table(state, 1, 2, lua_gettop(state)); + } + } return 0; } diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index d043d96f7..64ebf4de6 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -149,6 +149,13 @@ namespace DFHack { namespace LuaWrapper { */ uint8_t *get_object_addr(lua_State *state, int obj, int field, const char *mode); + bool is_type_compatible(lua_State *state, type_identity *type1, int meta1, + type_identity *type2, int meta2, bool exact_equal); + + type_identity *get_object_identity(lua_State *state, int objidx, + const char *ctx, bool allow_type = false, + bool keep_metatable = false); + void LookupInTable(lua_State *state, void *id, const char *tname); void SaveInTable(lua_State *state, void *node, const char *tname); void SaveTypeInfo(lua_State *state, void *node);