Фон
Я работаю с Ватусимото над игрой 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)
Как и с любой загадкой, вам нужно будет снимать ее слой за слоем. Я рекомендую пройти те же шаги, что и Lua, и попытаться определить, где выбранный путь отличается от ваших ожиданий:
Что значит getmetatable(levelgen).__index
вернуть? Если это таблица, то проверьте ее содержимое на addItem
, Если это функция, попробуйте вызвать ее с помощью (table, "addItem")
и посмотрим, что он вернет.
Проверить, если getmetatable
возвращает ссылку на один и тот же объект до и после вызова (или в случае сбоя).
Есть ли несколько уровней метатериальной косвенности, через которую проходит вызов? Если это так, попробуйте пойти по тому же пути с явными вызовами и посмотреть, где различия.
Ты используешь weak
ключи, которые могут привести к исчезновению значений, если нет других ссылок?
Можете ли вы предоставить значение «по умолчанию», когда обнаружите, что это не удалось, и продолжите проверять, «найдет» ли он этот метод позже? Или когда он сломан, он сломан для каждого звонка после этого?
Что если вы сохраните правильное значение для addItem и «исправите» его, когда обнаружите, что оно сломано?
Что если вы просто обработаете ошибку (как и вы) и вызовете ее 10 раз? Покажет ли он действительные результаты хотя бы один раз (после сбоя)? 100 раз? Если вы продолжаете вызывать один и тот же метод, когда он работает, он потерпит неудачу? Это может помочь вам придумать более воспроизводимую ошибку.
Я не знаком с LuaWrapper, чтобы давать более конкретные вопросы, но именно эти шаги я бы предпринял на вашем месте.
Я сильно подозреваю, что проблема в том, что у вас есть класс или структура, подобные этой:
struct Foo
{
Bar bar;
// Other fields follow
}
И что вы выставили Lu и Foo и Bar через LuaWrapper. Важным моментом здесь является то, что bar
это первое поле на вашем Foo
структура. В качестве альтернативы, у вас может быть некоторый класс, который наследуется от некоторого другого базового класса, и как производный, так и базовый класс доступны LuaWrapper.
LuaWrapper использует функцию с именем Identifier для уникального отслеживания каждого объекта (например, был ли данный объект уже добавлен в состояние Lua). По умолчанию он использует адрес объекта в качестве ключа. В случаях, подобных описанному выше, возможно, что и Foo, и Bar имеют одинаковый адрес в памяти, и, таким образом, LuaWrapper может запутаться.
Это может привести к получению метатабельного объекта неправильного объекта при попытке поиска метода. Понятно, что, поскольку он просматривает неправильную метатаблицу, он не найдет нужный вам метод, и поэтому он будет выглядеть так, как будто ваша метатаблица имеет загадочно потерянные записи.
Я зарегистрировал изменение, которое отслеживает данные каждого объекта по типу, а не по одной гигантской куче. Если вы обновите свою копию LuaWrapper до последней из репозитория, я уверен, что ваша проблема будет исправлена.
После слияния с восходящим (коммит 3c54015) LuaWrapper эта проблема исчезла. Похоже, что это ошибка в LuaWrapper.
Спасибо Алекс!