Lua,C++和穷人的子类

Wat*_*oto 11 c++ oop lua

我是Bitfighter的首席开发,我们正在使用Lua和C++的混合,使用Lunar(Luna的一种变体,可在此处获得)将它们绑定在一起.

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

这就是我所拥有的:

C++类结构

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

    Robot

机器人实现了一个调用的方法getFiringSolution(GameItem项目),着眼于位置和速度项目,并返回该机器人将需要火打角项目.

-- This is in Lua
angle = robot:getFiringSolution(rock)
if(angle != nil) then
    robot:fire(angle)
end
Run Code Online (Sandbox Code Playgroud)

所以我的问题是,我想通过岩石,石头,或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
}
Run Code Online (Sandbox Code Playgroud)

理想情况下,我想要做的是这样的事情:

// This is C++, doesn't work
S32 Robot::getFiringSolution(lua_State *L) 
{
   GameItem *target = Lunar<GameItem>::check(L, 1);
   return returnFloat(L, getFireAngle(target));    
}
Run Code Online (Sandbox Code Playgroud)

这个潜在的解决方案不起作用,因为Lunar的检查函数希望堆栈上的对象具有与GameItem定义的className相匹配的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));    
}
Run Code Online (Sandbox Code Playgroud)

这个解决方案的问题是,如果堆栈上的项目类型不正确,check函数会生成错误,并且我相信,从堆栈中删除了感兴趣的对象,所以我只有一次尝试抓取它.

我想我需要从堆栈中获取指向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
}
Run Code Online (Sandbox Code Playgroud)

如果我这么称呼它:

GameItem *target = Lunar<Rock>::check(L, 1);
Run Code Online (Sandbox Code Playgroud)

然后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;
   }
}
Run Code Online (Sandbox Code Playgroud)

然后,以后......

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));    
}
Run Code Online (Sandbox Code Playgroud)

我可以对此进行进一步的优化......我真的想弄清楚如何将其折叠成一个循环,因为实际上,我将有三个以上的类来处理,这个过程是有点累赘.

上述解决方案略有改进

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();
   }
}
Run Code Online (Sandbox Code Playgroud)

然后,以后......

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  
}
Run Code Online (Sandbox Code Playgroud)

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 
Run Code Online (Sandbox Code Playgroud)

用户从Lua这样打电话:

   angle = getFiringSolution( item )
Run Code Online (Sandbox Code Playgroud)

Nor*_*sey 5

我想你正试图在错误的地方调度方法.(这个问题是所有这些"自动化"方式使Lua与C或C++交互的困难的症状:对于它们中的每一个,幕后都会有一些魔力,并且如何使其工作并不总是显而易见的.我不明白为什么更多的人不只是使用Lua的C API.)

我看了一下Lunar网页,它看起来好像你需要methods在类型上创建一个表T然后调用该Luna<T>::Register方法.网上有一个简单的例子.如果我正确地阅读了代码,那么问题中的粘合代码实际上并不是推荐使用Lunar的方法.(我还假设你可以完全像C++调用那样实现这些方法.)

这一切都非常狡猾,因为Lunar的文档很薄.一个明智的选择是自己完成所有工作,并将每个C++类型与包含其方法的Lua表相关联.然后你有Lua __indexmetamethod咨询那张桌子,鲍勃是你的叔叔.Lunar正在做一些接近这些的东西,但它充分利用了C++模板,其他goo我不确定如何使它工作.

模板的东西非常聪明.您可能希望花时间深入了解其工作原理,或者重新考虑是否以及如何使用它.

简介:为每个类创建一个显式方法表,并使用Lunar Register方法注册每个类.或者自己滚动.


Ale*_*ysh 1

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

另外,如果您指定您使用的 Lunar 版本也很好(指向它的链接会很棒)。

如果是这个,那么类类型就存储在Lua对象元表中(也可以说这个元表就是类型)。

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

如果您喜欢修补 Lunar,一种可能的方法是__lunarClassName向元表添加一些字段并存储T::name在那里。然后提供lunar_typename()C++ 函数(在 Lunar 模板类之外——因为我们不需要T那里),并从中返回__lunarClassName参数元表的该字段的值。(不要忘记检查对象是否有元表,并且元表有这样的字段。)您可以通过调用lunar_typename()then来检查Lua对象类型。

来自个人经验的一点建议:向 Lua 推送的业务逻辑越多越好。除非您受到严重的性能限制的压力,否则您可能应该考虑将所有层次结构转移到 Lua ——您的生活会变得更加简单。

如果我可以进一步帮助您,请说出来。

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

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

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

dispatch =
{
  [Rock] = Robot.getFiringSolutionForRock;
  [Stone] = Robot.getFiringSolutionForStone;
  [RockyStone] = Robot.getFiringSolutionForRockyStone;
}
Run Code Online (Sandbox Code Playgroud)

如果我是对的,下一行应该调用机器人对象的正确专用方法。

dispatch[getmetatable(rock)](robot, rock)
Run Code Online (Sandbox Code Playgroud)