Lua,C ++和穷人的子类

时间:2009-05-14 23:23:04

标签: c++ oop lua

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

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

这就是我所拥有的:

C ++类结构

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

    Robot

Robot实现一个名为 getFiringSolution(GameItem item)的方法,该方法查看 item 的位置和速度,并返回机器人需要触发的角度点击项目

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

所以我的问题是我想将 rocks stone 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的检查函数希望堆栈上的对象具有与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));    
}

这个解决方案的问题是,如果堆栈上的项目类型不正确,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
}

如果我这么称呼它:

GameItem *target = Lunar<Rock>::check(L, 1);

然后luaL_checkudata()检查堆栈上的项是否为Rock。如果是这样,一切都是桃子的,它会返回一个指向我的Rock对象的指针,该对象会被传递回getFiringSolution()方法。如果堆栈中存在非Rock项,则转换为null,并且调用luaL_typerror(),将app发送到lala land(错误处理打印诊断并以极端偏见终止机器人)。 / p>

关于如何推进这项工作的任何想法?

非常感谢!!

我提出的最佳解决方案......丑陋,但有效

根据以下建议,我想出了这个:

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 )

4 个答案:

答案 0 :(得分:5)

我认为你试图在错误的地方进行方法调度。 (这个问题是所有这些“自动化”方式使Lua与C或C ++交互的困难的症状:对于他们每个人来说,幕后都有一些魔力,而且并非总是如此很明显如何让它工作。我不明白为什么更多的人不只是使用Lua的C API。)

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

这一切都非常狡猾,因为Lunar的文档很薄。 一个明智的选择是自己完成所有工作,并将每个C ++类型与包含其方法的Lua表相关联。然后你有Lua __index metamethod咨询该表,Bob是你的叔叔。 Lunar正在为这些做一些事情 close ,但它充分利用了C ++模板和其他goo,我不知道如何让它工作。

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

摘要:为每个类创建一个显式方法表,并使用Lunar Register方法注册每个类。或者自己动手。

答案 1 :(得分:1)

您应该告诉我们您的代码中究竟什么不起作用。我认为所有非岩石都失败了Lunar<Rock>::check(L, 1)。我是对的吗?

如果您指定使用哪个版本的Lunar(指向它的链接会很棒),那也没关系。

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

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

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

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

如果我可以帮助你,请说出来。

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

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

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

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

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

dispatch[getmetatable(rock)](robot, rock)

答案 2 :(得分:1)

我建议您在纯lua中定义面向对象的系统,然后为API的那个方面编写一个自定义绑定到C ++。

Lua非常适合原型OO实现,其中表用于模拟类,其中一个条目具有一个名为new的函数,当调用它时返回一个相同“类型”的适当表。

但是,从C ++开始,创建一个具有.invoke方法的LuaClass,接受一个C字符串(即一个以null结尾的const char数组)来指定要调用的成员函数的名称,并取决于如何你想要处理变量参数,将这个.invoke方法的几个模板化版本用于零,一,二,...... N个参数作为必要,或者定义一个将可变数量的参数传递给它的方法,并且有很多种方法这样做。

对于Lua,我建议制作两个.invoke方法,一个期望std :: vector,另一个期望一个std :: map,但我会把它留给你。 :)

在我上一次的Lua / C ++项目中,我只使用了以空字符结尾的C字符串数组,要求lua将字符串转换为适当的值。

享受。

答案 3 :(得分:0)

我面临着相同的需求,这就是我想出的。 (我必须对Lunar标题做一些小改动)

首先,我为包含Lua方法的所有类添加了一个全局“接口”。 我理解这可能看起来不如“原始”方式灵活,但在我看来它更清晰,我确实需要它来执行动态演员表。

class LuaInterface
{
  public:
    virtual const char* getClassName() const=0;
};

是的,它只包含一个纯虚方法,它显然会返回派生类中的静态“className”属性。这样,您可以拥有多态性,并保留模板化月球类所需的静态名称成员。

为了让我的生活更轻松,我还添加了一些定义:

#define LuaClass(T) private: friend class Lunar<T>; static const char className[]; static Lunar<T>::RegType methods[]; public: const char* getClassName() const { return className; }

所以你基本上只需要声明一个这样的类:

class MyLuaClass: public LuaInterface
{
   LuaClass(MyLuaClass)
   public:
   MyLuaMethod(lua_State* L);
};

这里没什么特别的。

我还需要一个“单身人士”(哎哟,我知道:它不一定是一个单身人士只是做任何你想做的事情)

class LuaAdapter
{
  //SINGLETON part : irrelevant
  public:

  const lua_State* getState() const { return _state; }
  lua_State* getState() { return _state; }

  template <class T>
  void registerClass(const std::string &name) 
  { 
    Lunar<T>::Register(_state); 
    _registeredClasses.push_back(name);
  }

  void registerFunction(const std::string &name, lua_CFunction f)
  {
    lua_register(_state, name.c_str(), f);
    _registeredFunctions.push_back(name);
  }

  bool loadScriptFromFile(const std::string &script);
  bool loadScript(const std::string &script);

  const StringList& getRegisteredClasses() const { return _registeredClasses; }
  const StringList& getRegisteredFunctions() const { return _registeredFunctions; }

  LuaInterface* getStackObject() const;

  private:

  lua_State*        _state;
  StringList        _registeredClasses;
  StringList        _registeredFunctions;

};

现在,只需看看registerClass方法:我们将其名称存储在StringList中(只是一个字符串列表)

现在,我们的想法是实现一个代理来注册我们的类:

template<class _Type>
class RegisterLuaClassProxy
{
  public:
  RegisterLuaClassProxy(const std::string &name)
  {
     LuaAdapter::instance()->registerClass<_Type>(name);
  }

  ~RegisterLuaClassProxy()
  {
  }
};

我们需要为每个LuaInterface类构建每个代理的一个实例。 ie:在MyClass.cpp中,在标准的“Lunar”方法声明之后:

RegisterLuaClass(MyClass)

再次提出了几个定义:

#define RegisterLuaClassWithName(T, name) const char T::className[] = name; RegisterLuaClassProxy<T> T ## _Proxy(name);
#define RegisterLuaClass(T) RegisterLuaClassWithName(T, #T)

对“functions”方法/ proxy执行相同的操作。

现在农历标题略有变化:

从类中删除“userdataType”结构,并在类外定义一个结构:

typedef struct { LuaInterface *pT; } userdataType;

(请注意,您还需要在Lunar类中添加一些static_cast)

好吧,好吧。现在我们拥有执行操作所需的所有结构,我已根据您的代码在LuaAdapter的getStackObject()方法中定义了它。

LuaInterface* LuaAdapter::getStackObject() const
{
lua_getmetatable(_state, 1);
for(StringList::const_iterator it = _registeredClasses.begin(); it != _registeredClasses.end(); ++it)
{
    // CHECK ITEM
    luaL_getmetatable(_state, it->c_str());
    if(lua_rawequal(_state, -1, -2)) // Lua object on stack is of class <T>
    {
        lua_pop(_state, 2); // Remove both metatables
        userdataType *ud = static_cast<userdataType*>(luaL_checkudata(_state, 1, it->c_str()));
        if(!ud) luaL_typerror(_state, 1, it->c_str());
        return ud->pT;
    }
    else // Object on stack is something else
    {
        // Remove <T>'s metatable, leave the other in place for further comparison
        lua_pop(_state, 1);
    }
}
return NULL;
}

这是诀窍:由于返回的指针指向抽象类,因此可以安全地使用dynamic_cast&lt;&gt;用它。并添加一些“中间”抽象类,使用漂亮的虚拟方法,如:

int fire(lua_State *L)
{
GameItem *item = dynamic_cast<GameItem*>(LuaAdapter::instance()->getStackObject());
if( item!= NULL)
{
        item->fire();
}   
return 0;
}

......我希望这会有所帮助。不要犹豫,纠正我/添加内容/反馈。

干杯:)