代码之家  ›  专栏  ›  技术社区  ›  Watusimoto

Lua中陈旧C++引用的检测

  •  2
  • Watusimoto  · 技术社区  · 15 年前

    我是公司的首席开发人员 Bitfighter 一个游戏,主要用C++编写,但是用Lua来编写机器人的脚本。我们正在使用 Lunar (Luna的一种变体)将钻头粘在一起。

    我现在正在努力研究我们的Lua脚本如何知道他们有一个引用的对象已经被C++代码删除了。

    if needTarget then                           -- needTarget => global(?) boolean
        ship = findClosest(findItems(ShipType))  -- ship => global lightUserData obj
    end
    
    if ship ~= nil then
        bot:setAngleToPoint(ship:getLoc())
        bot:fire()
    end
    

    请注意,仅当needTarget为true时才设置ship,否则将使用来自上一次迭代的值。自上次设置变量以来,很有可能(甚至,如果bot一直在做它的工作:-),飞船已经被杀死(并且它的对象被C++删除)。如果是这样,C++将在调用St:GeLoCo()时有一个合适的值,并且通常会崩溃。

    因此,问题是如何最优雅地处理这种情况,并在程序员出错时限制损害。

    我有一些想法。首先,我们可以创建某种类型的Lua函数,C++代码可以在船舶或其他项目死亡时调用它。

    function itemDied(deaditem)
        if deaditem == ship then
            ship = nil
            needTarget = true
        end
    end
    

    其次,我们可以实现某种引用计数智能指针来“神奇地”解决问题。但我不知道从哪里开始。

    第三,我们可以有某种死区检测器(不确定如何工作),机器人可以这样调用:

    if !isAlive(ship) then
        needTarget = true
        ship = nil             -- superfluous, but here for clarity in this example
    end
    
    if needTarget then                           -- needTarget => global(?) boolean
        ship = findClosest(findItems(ShipType))  -- ship => global lightUserData obj
    end
    
    <...as before...>
    

    第四,我只能保留船的ID,而不是引用,并使用该ID在每个周期获取船对象,如下所示:

    local ship = getShip(shipID)                 -- shipID => global ID
    
    if ship == nil then
        needTarget = true
    end
    
    if needTarget then                           -- needTarget => global(?) boolean
        ship = findClosest(findItems(ShipType))  -- ship => global lightUserData obj
        shipID = ship:getID()
    end
    
    <...as before...>
    

    我的理想情况也是明智地抛出错误。如果我在一艘死船上运行getLoc()方法,我希望触发错误处理代码,让机器人有机会恢复,或者至少让系统杀死机器人并记录问题,希望提醒我在编写机器人代码时要更加小心。

    这些是我的想法。我倾向于#1,但它感觉很笨重(可能会涉及很多来回,因为我们有很多像子弹这样的短生命周期对象要处理,其中大部分我们不会跟踪)。可能很容易忘记实现itemdired()函数#2很吸引人,因为我喜欢魔术,但不知道它是如何工作的#三,#4是非常容易理解的,我可以将我的死区检测限制在几个游戏周期内(最有可能是一艘飞船)感兴趣的少数对象上。

    这一定是一个常见的问题。你觉得这些想法怎么样?还有更好的吗?

    谢谢


    在C++中,我的船对象被称为Stand,其生命周期由C++控制。对于每个Ship,我创建一个称为LuaShip的代理对象,它包含一个指向Ship的指针,Ship包含一个指向LuaShip的指针。在飞船的析构函数中,我将LuaShip的飞船指针设置为NULL,我将其用作飞船已被摧毁的指示器。

    我的Lua代码只有一个对LuaShip的引用,因此(至少在理论上,由于这部分仍然不能正常工作),Lua将在相应的Ship对象消失后控制LuaShip的生命周期。所以Lua总是有一个有效的句柄,即使在Ship对象消失之后,我也可以为Ship方法编写代理方法来检查Ship是否为NULL。

    因此,现在我的任务是更好地理解Luna/Lunar如何管理指针的生命周期,并确保我的Luaship不会在其合作伙伴飞船被删除时被删除,如果仍然有一些Lua代码指向它们。这应该是非常可行的。


    使用前,由Lua编码器检查船舶是否仍然有效。如果他们不这样做,我就可以抓住这个位置,抛出一条严厉的错误信息,而不是让整个游戏崩溃(就像以前发生的那样)。

    现在Lua已经完全控制了LuSpHIP的生命周期,C++可以删除船只而不会造成问题,而且一切看起来都很顺利。唯一的缺点是我可能会创建很多LuaShip对象,但实际上并没有那么糟糕。


    如果您对这个主题感兴趣,请参阅我发布的关于一个相关概念的邮件列表线程,该线程以完善上述内容的一些建议结尾:

    http://lua-users.org/lists/lua-l/2009-07/msg00076.html

    4 回复  |  直到 15 年前
        1
  •  5
  •   MSalters    15 年前

    我认为你的Lua方面没有问题,你不应该在那里解决它。

    您的C++代码正在删除仍在引用的对象。不管它们是如何被引用的,那都很糟糕。

        2
  •  4
  •   David Seiler    15 年前

    我们公司采用了第四种解决方案,对我们来说效果很好。我推荐它。但是,为了完整性:

    1号是实心的。让飞船的析构函数调用一些月球代码(或者标记它无论如何都应该被调用),然后如果你找不到它就抱怨。这样做意味着您必须非常小心,如果您想在单独的线程中运行游戏引擎和机器人,可能需要稍微修改Lua运行时。

    第2号并不像你想象的那么难:在C++侧写或借用一个引用计数指针,如果你的Lua /C++胶习惯于处理C++指针,它可能会不需要进一步的干预,除非你在运行时或通过检查符号表来生成绑定。问题是,它会迫使你的设计发生相当深刻的变化;如果你使用引用计数指针来引用飞船,你必须在任何地方都使用它们——引用混合了裸指针和智能指针的飞船所固有的风险应该是显而易见的。所以我不会走那条路,不会像你看起来那么晚。

    第三个问题很棘手。您需要一种方法来确定给定的ship对象是活的还是死的,即使在表示它的内存被释放之后也是如此。我能想到的解决这个问题的所有方法基本上都可以归结为第4种:你可以让死船留下某种标记,它被复制到Lua对象中,可以用来检测死亡(你可以把死船保存在一个std::set或类似的东西中),但是为什么不通过它们的标记来引用船呢?

    ship:getLoc() 只有在析构函数中采取特殊操作,才能在已删除的船上执行。这个问题没有完美的解决方案,祝你好运。

        3
  •  2
  •   Sean    11 年前

    这是一个古老的问题,但国际海事组织,正确的解决办法是 lua_newuserdata() shared_ptr weak_ptr 通过任何一个 boost::shared_ptr / boost::weak_ptr C++11 std::shared_ptr / std::weak_ptr . 从那里,您可以在需要时创建引用,如果 弱ptr 无法获得 lock() A. . 例如(使用Boost的 共享ptr C++11 不过,对于新项目,如果可能的话,我建议使用C++11 共享ptr

    using MyObjectPtr = boost::shared_ptr<MyObject>;
    using MyObjectWeakPtr = boost::weak_ptr<MyObject>;
    
    auto mySharedPtr = boost::make_shared<MyObject>();
    auto userdata = static_cast<MyObjectWeakPtr*>(lua_newuserdata(L, sizeof(MyObjectWeakPtr)));
    new(userdata) MyObjectWeakPtr(mySharedPtr);
    

    auto weakObj = *static_cast<MyObjectWeakPtr*>(
        luaL_checkudata(L, 1, "MyObject.Metatable"));
    luaL_argcheck(L, weakObj != nullptr, 1, "'MyObjectWeakPtr' expected");
    
    // If you're using a weak_ptr, this is required!!!! If your userdata is a
    // shared_ptr, you can just act on the shared_ptr after luaL_argcheck()
    if (auto obj = weakObj.lock()) {
      // You have a valid shared_ptr, the C++ object is alive and you can
      // dereference like a normal shared_ptr.
    } else {
      // The C++ object went away, you can safely garbage collect userdata
    }
    

    重要的是,不要忘记取消分配 __gc 元方法:

    static int
    myobject_lua__gc(lua_State* L) {
      auto weakObj = *static_cast<MyObjectWeakPtr*>(
          luaL_checkudata(L, 1, "MyObject.Metatable"));
      luaL_argcheck(L, weakObj != nullptr, 1, "'MyObjectWeakPtr' expected");
      weakObj.~MyObjectWeakPtr();
    }
    

    不要忘记使用宏或模板元编程来避免代码重复: static_cast<> luaL_argcheck()

    使用 只要Lua对象还存在,就需要保持C++对象生存。使用 当C++可以收获对象时,它可以从Lua的脚下消失。始终使用其中一个 共享ptr 弱ptr

    boost::enable_shared_from_this std::enable_shared_from_this 因为它允许使用 shared_from_this()

        4
  •  0
  •   Community CDub    8 年前

    我同意 MSalters 我真的不认为你应该把内存从C++端释放出来。LuaUserData支持__; gc元方法,让您有机会进行清理。如果gc不够强大,您可以稍微调整它,或者更经常地以小步数手动运行它。lua-gc不是确定性的,因此如果需要释放资源,则需要有一个可以调用的函数来释放这些资源(也将由uu-gc调用,并进行适当的检查)。

    weak tables 对于您的飞船引用,这样您就不必将每个引用都分配给nil来释放它。如果有一个强引用(例如,在所有活动船舶的列表中),那么所有其他船舶都是弱引用。当一艘船被摧毁时,在船上设置一个标记该船的标志,然后在active ships表中将引用设置为nil。然后,当另一艘船想要互动时,你的逻辑是相同的,除了你检查:

    if ship==nil or ship.destroyed then
      ship = findClosest(findItems(ShipType))
    end