Lua, c ++ и исчезающие метатаблицы

Фон

Я работаю с Ватусимото над игрой Bitfighter. Мы используем вариацию LuaWrapper для соединения наших объектов c ++ с объектами Lua в игре. Мы также используем вариацию Lua под названием Lua-VEC ускорить векторные операции.

В течение некоторого времени мы работали над устранением ошибки, которая ускользнула от нас. Произойдут случайные сбои, предполагающие наличие поврежденных метатаблиц. Увидеть Вот за пост Ватусимото по этому вопросу. Я не уверен, что это из-за испорченной метатабельности и видел действительно странное поведение, о котором я хотел бы спросить здесь.

Проблема Проявление

В качестве примера мы создаем объект и добавляем его на уровень, подобный следующему:

t = TextItem.new()
t:setText("hello")
levelgen:addItem(t)

Однако игра иногда (не всегда) вылетает. С ошибкой:

attempt to call missing or unknown method 'addItem' (a nil value)

Используя предложение, данное в ответе на пост Ватусимото, упомянутый выше, я изменил последнюю строку на следующую:

local ok, res = pcall(function() levelgen:addItem(t) end)

if not ok then
local s = "Invalid levelgen value: "..tostring(levelgen).." "..type(levelgen).."\n"
for k, v in pairs(getmetatable(levelgen)) do
s = s.."meta "..tostring(k).." "..tostring(v).."\n"end

error(res..s)
end

Это распечатывает метатаблицу для levelgen если что-то не так, вызывая метод из него.

Тем не менее, и это безумие, когда он терпит неудачу и распечатывает метатабельный, метатабельный именно так как это должно быть (при правильном addItem позвони и все). Если я распечатаю метатаблицу для levelgen при загрузке скрипта и при неудачном использовании pcall выше они идентичны, каждый вызов и указатель на userdata одинаковы и должны быть.

Это как будто метатабельный для levelgen самопроизвольно исчезает наугад.

Кто-нибудь знает, что происходит?

Спасибо

Замечания: Это не происходит только с levelgen объект. Например, это произошло на TestItem объект, упомянутый выше, а также. На самом деле, тот же код вылетает на моем компьютере в строке levelgen:addItem(t) но вылетает на компьютере другого разработчика с линией t:setText("hello") с тем же сообщением об ошибке missing or unknown method 'setText' (a nil value)

5

Решение

Как и с любой загадкой, вам нужно будет снимать ее слой за слоем. Я рекомендую пройти те же шаги, что и Lua, и попытаться определить, где выбранный путь отличается от ваших ожиданий:

Что значит getmetatable(levelgen).__index вернуть? Если это таблица, то проверьте ее содержимое на addItem, Если это функция, попробуйте вызвать ее с помощью (table, "addItem") и посмотрим, что он вернет.

Проверить, если getmetatable возвращает ссылку на один и тот же объект до и после вызова (или в случае сбоя).

Есть ли несколько уровней метатериальной косвенности, через которую проходит вызов? Если это так, попробуйте пойти по тому же пути с явными вызовами и посмотреть, где различия.

Ты используешь weak ключи, которые могут привести к исчезновению значений, если нет других ссылок?

Можете ли вы предоставить значение «по умолчанию», когда обнаружите, что это не удалось, и продолжите проверять, «найдет» ли он этот метод позже? Или когда он сломан, он сломан для каждого звонка после этого?

Что если вы сохраните правильное значение для addItem и «исправите» его, когда обнаружите, что оно сломано?

Что если вы просто обработаете ошибку (как и вы) и вызовете ее 10 раз? Покажет ли он действительные результаты хотя бы один раз (после сбоя)? 100 раз? Если вы продолжаете вызывать один и тот же метод, когда он работает, он потерпит неудачу? Это может помочь вам придумать более воспроизводимую ошибку.

Я не знаком с LuaWrapper, чтобы давать более конкретные вопросы, но именно эти шаги я бы предпринял на вашем месте.

2

Другие решения

Я сильно подозреваю, что проблема в том, что у вас есть класс или структура, подобные этой:

struct Foo
{
Bar bar;
// Other fields follow
}

И что вы выставили Lu и Foo и Bar через LuaWrapper. Важным моментом здесь является то, что bar это первое поле на вашем Foo структура. В качестве альтернативы, у вас может быть некоторый класс, который наследуется от некоторого другого базового класса, и как производный, так и базовый класс доступны LuaWrapper.

LuaWrapper использует функцию с именем Identifier для уникального отслеживания каждого объекта (например, был ли данный объект уже добавлен в состояние Lua). По умолчанию он использует адрес объекта в качестве ключа. В случаях, подобных описанному выше, возможно, что и Foo, и Bar имеют одинаковый адрес в памяти, и, таким образом, LuaWrapper может запутаться.

Это может привести к получению метатабельного объекта неправильного объекта при попытке поиска метода. Понятно, что, поскольку он просматривает неправильную метатаблицу, он не найдет нужный вам метод, и поэтому он будет выглядеть так, как будто ваша метатаблица имеет загадочно потерянные записи.

Я зарегистрировал изменение, которое отслеживает данные каждого объекта по типу, а не по одной гигантской куче. Если вы обновите свою копию LuaWrapper до последней из репозитория, я уверен, что ваша проблема будет исправлена.

2

После слияния с восходящим (коммит 3c54015) LuaWrapper эта проблема исчезла. Похоже, что это ошибка в LuaWrapper.

Спасибо Алекс!

1
По вопросам рекламы [email protected]