我是Bitfighter的首席开发人员,我们正在使用Lunar和C++进行混合,使用Lunar(Luna的变体,可用here)将它们绑定(bind)在一起。

我知道这个环境对面向对象和继承没有很好的支持,但是我想找到一种方法来至少部分地解决这些限制。

这是我所拥有的:

C++类结构

    GameItem
       |---- Rock
       |---- Stone
       |---- RockyStone

    Robot

Robot implements a method called getFiringSolution(GameItem item) that looks at the position and speed of item, and returns the angle at which the robot would need to fire to hit item.

-- This is in Lua
angle = robot:getFiringSolution(rock)
if(angle != nil) then
    robot:fire(angle)
end

所以我的问题是我想将岩石,石头或rockyStones传递给getFiringSolution方法,但我不确定该怎么做。

这仅适用于Rocks:
// C++ code
S32 Robot::getFiringSolution(lua_State *L)
{
   Rock *target = Lunar<Rock>::check(L, 1);
   return returnFloat(L, getFireAngle(target));    // returnFloat() is my func
}

理想情况下,我想做的是这样的:
// This is C++, doesn't work
S32 Robot::getFiringSolution(lua_State *L)
{
   GameItem *target = Lunar<GameItem>::check(L, 1);
   return returnFloat(L, getFireAngle(target));
}

这个潜在的解决方案不起作用,因为Lunar的check函数希望堆栈上的对象具有与GameItem定义的类名匹配的className。 (对于您在Lunar中注册的每种对象类型,请以字符串形式提供名称,Lunar会使用该名称来确保对象的类型正确。)

我会满足于这样的事情,在这里我必须检查每个可能的子类:
// Also C++, also doesn't work
S32 Robot::getFiringSolution(lua_State *L)
{
   GameItem *target = Lunar<Rock>::check(L, 1);
   if(!target)
       target = Lunar<Stone>::check(L, 1);
   if(!target)
       target = Lunar<RockyStone>::check(L, 1);

   return returnFloat(L, getFireAngle(target));
}

这种解决方案的问题在于,如果堆栈中的项目类型不正确,则检查功能会生成错误,并且我相信将从堆栈中删除感兴趣的对象,因此我只有一次尝试来捕获它。

我在想我需要从堆栈中获取一个指向Rock/Stone/RockyStone对象的指针,弄清楚它是什么类型,然后在使用它之前将其强制转换为正确的对象。

Lunar进行类型检查的关键部分是:
// from Lunar.h
// get userdata from Lua stack and return pointer to T object
static T *check(lua_State *L, int narg) {
  userdataType *ud =
    static_cast<userdataType*>(luaL_checkudata(L, narg, T::className));
  if(!ud) luaL_typerror(L, narg, T::className);
  return ud->pT;  // pointer to T object
}

如果我这样称呼它:
GameItem *target = Lunar<Rock>::check(L, 1);

然后luaL_checkudata()检查堆栈上的项目是否为Rock。如果是这样,则所有内容都是桃红色的,并且返回指向我的Rock对象的指针,该对象被传递回getFiringSolution()方法。如果堆栈上有一个非Rock项目,则强制类型转换返回null,并调用luaL_typerror(),这会将应用程序发送到lala land(错误处理将打印诊断并以极高的偏见终止机器人)。

关于如何继续前进的任何想法?

非常感谢!!

我想出的最好的解决方案...很丑,但是可​​以用

根据以下建议,我提出了以下建议:
template <class T>
T *checkItem(lua_State *L)
{
   luaL_getmetatable(L, T::className);
   if(lua_rawequal(L, -1, -2))         // Lua object on stack is of class <T>
   {
      lua_pop(L, 2);                   // Remove both metatables
      return Lunar<T>::check(L, 1);    // Return our object
   }
   else                                // Object on stack is something else
   {
      lua_pop(L, 1);    // Remove <T>'s metatable, leave the other in place
                        // for further comparison
      return NULL;
   }
}

然后,以后...
S32 Robot::getFiringSolution(lua_State *L)
{
   GameItem *target;

   lua_getmetatable(L, 1);    // Get metatable for first item on the stack

   target = checkItem<Rock>(L);

   if(!target)
      target = checkItem<Stone>(L);

   if(!target)
      target = checkItem<RockyStone>(L);

   if(!target)    // Ultimately failed to figure out what this object is.
   {
      lua_pop(L, 1);                      // Clean up
      luaL_typerror(L, 1, "GameItem");    // Raise an error
      return returnNil(L);                // Return nil, but I don't think this
                                          // statement will ever get run
   }

   return returnFloat(L, getFireAngle(target));
}

我可能会对此做进一步的优化……我真的很想弄清楚如何将其折叠成一个循环,因为实际上,我要处理的类不止三个。有点麻烦。

在上述解决方案上有一些改进

C++:
GameItem *LuaObject::getItem(lua_State *L, S32 index, U32 type)
{
  switch(type)
  {
    case RockType:
        return Lunar<Rock>::check(L, index);
    case StoneType:
        return Lunar<Stone>::check(L, index);
    case RockyStoneType:
        return Lunar<RockyStone>::check(L, index);
    default:
        displayError();
   }
}

然后,以后...
S32 Robot::getFiringSolution(lua_State *L)
{
   S32 type = getInteger(L, 1);                 // My fn to pop int from stack
   GameItem *target = getItem(L, 2, type);

   return returnFloat(L, getFireAngle(target)); // My fn to push float to stack
}

Lua辅助功能,作为一个单独的文件提供,以避免用户需要手动将其添加到他们的代码中:
function getFiringSolution( item )
  type = item:getClassID()      -- Returns an integer id unique to each class
  if( type == nil ) then
     return nil
   end
  return bot:getFiringSolution( type, item )
end

用户从Lua调用这种方式:
   angle = getFiringSolution( item )

最佳答案

您应该告诉我们在您的代码中到底什么不起作用。我想这是Lunar<Rock>::check(L, 1)对所有非Rocks都失败了。我对么?

如果您指定使用哪个Lunar版本,也可以(链接很好)。

如果它是this one,则类类型存储在Lua对象的元表中(可以说这个元表是该类型)。

看起来最简单的检查对象是否为Rock而不打补丁Lunar的最简单方法是调用luaL_getmetatable(L, Rock::className)来获取类metatable并将其与第一个参数的lua_getmetatable(L,1)进行比较(请注意第一个函数名称中的lua L ) 。这有点骇人听闻,但应该可以。

如果您对Lunar进行补丁修补没问题,一种可能的方法是将一些__lunarClassName字段添加到该元表中并在其中存储T::name。然后提供lunar_typename() C++函数(在Lunar模板类之外-那里我们不需要T),然后从中返回参数元表的__lunarClassName字段的值。 (不要忘了检查对象是否具有metatable,并且该metatable具有此类字段。)然后可以通过调用lunar_typename()来检查Lua对象类型。

个人经验的一些建议:插入Lua的业务逻辑越多越好。除非您受到严重的性能约束的压力,否则您可能应该考虑将所有层次结构移至Lua -您的生活会变得更加简单。

如果我可以进一步帮助您,请这样说。

更新:您更新帖子所用的解决方案看起来是正确的。

要在C语言中执行基于元表的调度,您可以使用例如类型的lua_topointer() luaL_getmetatable() 整数ojit_a值映射到知道如何处理该类型的函数对象/指针。

但是,我再次建议将该部分移至Lua。例如:从C++将类型特定的函数getFiringSolutionForRock()getFiringSolutionForStone()getFiringSolutionForRockyStone()导出到Lua。在Lua中,按元表存储方法表:

dispatch =
{
  [Rock] = Robot.getFiringSolutionForRock;
  [Stone] = Robot.getFiringSolutionForStone;
  [RockyStone] = Robot.getFiringSolutionForRockyStone;
}

如果我是对的,则下一行应调用机器人对象的正确专用方法。
dispatch[getmetatable(rock)](robot, rock)

09-28 08:55