From 125f4b129bba54448f14031ef510d6e35a4cffc2 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 9 Apr 2020 00:02:07 -0400 Subject: [PATCH 1/4] Add ref_target attribute to primitive field references --- library/LuaTypes.cpp | 11 +++++++++++ library/LuaWrapper.cpp | 10 ++++++++-- library/include/LuaWrapper.h | 11 ++--------- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 306538f51..4e90265ce 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -536,6 +536,7 @@ static void field_reference(lua_State *state, const struct_field_info *field, vo case struct_field_info::PRIMITIVE: case struct_field_info::SUBSTRUCT: push_object_internal(state, field->type, ptr); + get_object_ref_header(state, -1)->field_info = field; return; case struct_field_info::POINTER: @@ -706,6 +707,16 @@ static type_identity *find_primitive_field(lua_State *state, int field, const ch */ static int meta_primitive_index(lua_State *state) { + const char *attr = lua_tostring(state, -1); + if (attr == std::string("ref_target")) { + const struct_field_info *field_info = get_object_ref_header(state, 1)->field_info; + if (field_info && field_info->extra && field_info->extra->ref_target) { + LookupInTable(state, field_info->extra->ref_target, &DFHACK_TYPEID_TABLE_TOKEN); + } else { + lua_pushnil(state); + } + return 1; + } uint8_t *ptr = get_object_addr(state, 1, 2, "read"); auto type = find_primitive_field(state, 2, "read", &ptr); if (!type) diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 497addd5c..05c4db789 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -170,18 +170,24 @@ void LuaWrapper::push_object_ref(lua_State *state, void *ptr) // stack: [metatable] auto ref = (DFRefHeader*)lua_newuserdata(state, sizeof(DFRefHeader)); ref->ptr = ptr; + ref->field_info = NULL; lua_swap(state); lua_setmetatable(state, -2); // stack: [userdata] } -void *LuaWrapper::get_object_ref(lua_State *state, int val_index) +DFRefHeader *LuaWrapper::get_object_ref_header(lua_State *state, int val_index) { assert(!lua_islightuserdata(state, val_index)); auto ref = (DFRefHeader*)lua_touserdata(state, val_index); - return ref->ptr; + return ref; +} + +void *LuaWrapper::get_object_ref(lua_State *state, int val_index) +{ + return get_object_ref_header(state, val_index)->ptr; } /** diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index 4cbf9d34f..b23e1f912 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -126,6 +126,7 @@ namespace LuaWrapper { */ struct DFRefHeader { void *ptr; + const struct_field_info *field_info; }; /** @@ -133,15 +134,7 @@ namespace LuaWrapper { */ void push_object_ref(lua_State *state, void *ptr); DFHACK_EXPORT void *get_object_ref(lua_State *state, int val_index); - - /* - * The system might be extended to carry some simple - * objects inline inside the reference buffer. - */ - inline bool is_self_contained(DFRefHeader *ptr) { - void **pp = &ptr->ptr; - return **(void****)pp == (pp + 1); - } + DFHACK_EXPORT DFRefHeader *get_object_ref_header(lua_State *state, int val_index); /** * Report an error while accessing a field (index = field name). From 2108c2363dd22adc5bd24cea48217ca61834b04c Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 28 Apr 2020 01:21:11 -0400 Subject: [PATCH 2/4] Use strcmp --- library/LuaTypes.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 4e90265ce..ebc33b477 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -708,7 +708,7 @@ static type_identity *find_primitive_field(lua_State *state, int field, const ch static int meta_primitive_index(lua_State *state) { const char *attr = lua_tostring(state, -1); - if (attr == std::string("ref_target")) { + if (strcmp(attr, "ref_target") == 0) { const struct_field_info *field_info = get_object_ref_header(state, 1)->field_info; if (field_info && field_info->extra && field_info->extra->ref_target) { LookupInTable(state, field_info->extra->ref_target, &DFHACK_TYPEID_TABLE_TOKEN); @@ -717,6 +717,7 @@ static int meta_primitive_index(lua_State *state) } return 1; } + uint8_t *ptr = get_object_addr(state, 1, 2, "read"); auto type = find_primitive_field(state, 2, "read", &ptr); if (!type) From df6f3a04557525e5780fb423bf68b4caca4c2316 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 14 Jul 2020 02:31:18 -0400 Subject: [PATCH 3/4] Mark ref_target as a metafield so meta_primitive_newindex fails properly unit:_field('hist_figure_id').ref_target = 4 now fails with "Cannot write field int32_t.ref_target: builtin property or method" instead of "not found" --- library/LuaTypes.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 7f0711b20..845d273e3 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1316,6 +1316,8 @@ static void MakePrimitiveMetatable(lua_State *state, type_identity *type) { EnableMetaField(state, base+2, "value", type); AssociateId(state, base+3, 1, "value"); + + EnableMetaField(state, base+2, "ref_target", NULL); } // Add the iteration metamethods From 1f1bb5a055bbf121fbfe1a741200b7aeedac7cf1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 14 Jul 2020 02:54:33 -0400 Subject: [PATCH 4/4] Update Lua API docs for ref_target field, add tests --- docs/Lua API.rst | 4 +++- test/main.lua | 13 +++++++++++++ test/structures/ref_target.lua | 35 ++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 test/structures/ref_target.lua diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 4c7163623..2cfaff350 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -158,7 +158,9 @@ that don't fit any of the other reference types. Such references can only appear as a value of a pointer field, or as a result of calling the ``_field()`` method. -They behave as structs with one field ``value`` of the right type. +They behave as structs with a ``value`` field of the right type. If the +object's XML definition has a ``ref-target`` attribute, they will also have +a read-only ``ref_target`` field set to the corresponding type object. To make working with numeric buffers easier, they also allow numeric indices. Note that other than excluding negative values diff --git a/test/main.lua b/test/main.lua index 46a91e1ca..83a35d165 100644 --- a/test/main.lua +++ b/test/main.lua @@ -69,6 +69,19 @@ function expect.error(func, ...) return true end end +function expect.error_match(func, matcher, ...) + local ok, err = pcall(func, ...) + if ok then + return false, 'no error raised by function call' + elseif type(matcher) == 'string' then + if not tostring(err):match(matcher) then + return false, ('error "%s" did not match "%s"'):format(err, matcher) + end + elseif not matcher(err) then + return false, ('error "%s" did not satisfy matcher'):format(err) + end + return true +end function expect.pairs_contains(table, key, comment) for k, v in pairs(table) do if k == key then diff --git a/test/structures/ref_target.lua b/test/structures/ref_target.lua new file mode 100644 index 000000000..05818ee06 --- /dev/null +++ b/test/structures/ref_target.lua @@ -0,0 +1,35 @@ +function test.get() + dfhack.with_temp_object(df.unit:new(), function(unit) + expect.eq(unit:_field('hist_figure_id').ref_target, df.historical_figure) + end) +end + +function test.get_nil() + dfhack.with_temp_object(df.coord:new(), function(coord) + expect.nil_(coord:_field('x').ref_target) + end) +end + +function test.get_non_primitive() + dfhack.with_temp_object(df.unit:new(), function(unit) + expect.error_match(function() + return unit:_field('status').ref_target + end, 'not found') + end) +end + +function test.set() + dfhack.with_temp_object(df.unit:new(), function(unit) + expect.error_match(function() + unit:_field('hist_figure_id').ref_target = df.coord + end, 'builtin property or method') + end) +end + +function test.set_non_primitive() + dfhack.with_temp_object(df.unit:new(), function(unit) + expect.error_match(function() + unit:_field('status').ref_target = df.coord + end, 'not found') + end) +end