Merge remote-tracking branch 'lethosor/lua-ref-target' into develop

develop
lethosor 2020-07-15 00:04:37 -04:00
commit db004f484d
6 changed files with 75 additions and 12 deletions

@ -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, references can only appear as a value of a pointer field,
or as a result of calling the ``_field()`` method. 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 To make working with numeric buffers easier, they also allow
numeric indices. Note that other than excluding negative values numeric indices. Note that other than excluding negative values

@ -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::PRIMITIVE:
case struct_field_info::SUBSTRUCT: case struct_field_info::SUBSTRUCT:
push_object_internal(state, field->type, ptr); push_object_internal(state, field->type, ptr);
get_object_ref_header(state, -1)->field_info = field;
return; return;
case struct_field_info::POINTER: case struct_field_info::POINTER:
@ -706,6 +707,17 @@ static type_identity *find_primitive_field(lua_State *state, int field, const ch
*/ */
static int meta_primitive_index(lua_State *state) static int meta_primitive_index(lua_State *state)
{ {
const char *attr = lua_tostring(state, -1);
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);
} else {
lua_pushnil(state);
}
return 1;
}
uint8_t *ptr = get_object_addr(state, 1, 2, "read"); uint8_t *ptr = get_object_addr(state, 1, 2, "read");
auto type = find_primitive_field(state, 2, "read", &ptr); auto type = find_primitive_field(state, 2, "read", &ptr);
if (!type) if (!type)
@ -1304,6 +1316,8 @@ static void MakePrimitiveMetatable(lua_State *state, type_identity *type)
{ {
EnableMetaField(state, base+2, "value", type); EnableMetaField(state, base+2, "value", type);
AssociateId(state, base+3, 1, "value"); AssociateId(state, base+3, 1, "value");
EnableMetaField(state, base+2, "ref_target", NULL);
} }
// Add the iteration metamethods // Add the iteration metamethods

@ -170,18 +170,24 @@ void LuaWrapper::push_object_ref(lua_State *state, void *ptr)
// stack: [metatable] // stack: [metatable]
auto ref = (DFRefHeader*)lua_newuserdata(state, sizeof(DFRefHeader)); auto ref = (DFRefHeader*)lua_newuserdata(state, sizeof(DFRefHeader));
ref->ptr = ptr; ref->ptr = ptr;
ref->field_info = NULL;
lua_swap(state); lua_swap(state);
lua_setmetatable(state, -2); lua_setmetatable(state, -2);
// stack: [userdata] // 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)); assert(!lua_islightuserdata(state, val_index));
auto ref = (DFRefHeader*)lua_touserdata(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;
} }
/** /**

@ -126,6 +126,7 @@ namespace LuaWrapper {
*/ */
struct DFRefHeader { struct DFRefHeader {
void *ptr; void *ptr;
const struct_field_info *field_info;
}; };
/** /**
@ -133,15 +134,7 @@ namespace LuaWrapper {
*/ */
void push_object_ref(lua_State *state, void *ptr); void push_object_ref(lua_State *state, void *ptr);
DFHACK_EXPORT void *get_object_ref(lua_State *state, int val_index); DFHACK_EXPORT void *get_object_ref(lua_State *state, int val_index);
DFHACK_EXPORT DFRefHeader *get_object_ref_header(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);
}
/** /**
* Report an error while accessing a field (index = field name). * Report an error while accessing a field (index = field name).

@ -69,6 +69,19 @@ function expect.error(func, ...)
return true return true
end end
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) function expect.pairs_contains(table, key, comment)
for k, v in pairs(table) do for k, v in pairs(table) do
if k == key then if k == key then

@ -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