Lua、c++和消失的跖骨

Lua, c++ and disappearing metatables

本文关键字:消失 c++ Lua      更新时间:2023-10-16

背景

我和Watusimoto一起玩Bitfighter游戏。我们使用LuaWrapper的变体将我们的c++对象与游戏中的Lua对象连接起来。我们还使用Lua的一种变体Lua-vec来加速向量运算。

一段时间以来,我们一直在努力解决一个一直未能解决的错误。随机崩溃会发生,这表明元表已损坏。请参阅此处查看Watusimoto关于该问题的帖子。我不确定这是因为一个腐败的元表,我看到了一些非常奇怪的行为,我想在这里问一下。

问题表现

例如,我们创建一个对象并将其添加到如下级别:

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

然而,游戏有时(并非总是)会崩溃。出现错误:

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

使用回答Watusimoto上述帖子时给出的建议,我将最后一行改为:

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失败时,它们是相同的,每个调用和指向用户数据的指针都是相同的

就好像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
}

你已经通过LuaWrapper向Lua展示了Foo和Bar。这里重要的一点是barFoo结构上的第一个字段。或者,您可能有一些从其他基类继承的类,并且派生类和基类都公开给LuaWrapper。

LuaWrapper使用一个名为Identifier的函数来唯一地跟踪每个对象(比如给定对象是否已经添加到Lua状态)。默认情况下,它使用对象地址作为关键字。在上述情况下,Foo和Bar可能在内存中具有相同的地址,因此LuaWrapper可能会混淆。

这可能导致在试图查找方法时获取错误对象的元表。很明显,由于它看错了元表,它找不到你想要的方法,所以它看起来就像你的元表神秘地丢失了条目。

我检查了一个更改,它按类型跟踪每个对象的数据,而不是一大堆。如果您将您的副本LuaWrapper更新为存储库中的最新版本,我确信您的问题会得到解决。

与上游(commit 3c54015)LuaWrapper合并后,此问题已消失。这似乎是LuaWrapper中的一个错误。

谢谢亚历克斯!