11

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

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

这是我所拥有的:

C++ 类结构

    游戏物品
       |---- 摇滚
       |---- 石头
       |---- 洛基石

    机器人

Robot 实现了一个名为getFiringSolution(GameItem item)的方法,该方法查看item的位置和速度,并返回机器人需要开火才能击中item的角度。

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

所以我的问题是我想将rock 、 stone或rockyStones传递给getFiringSolution方法,但我不知道该怎么做。

这仅适用于岩石:

// 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 定义的类名匹配的类名。(对于您在 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 土地(错误处理会打印诊断信息并以极端偏见终止机器人)。

关于如何推进这一点的任何想法?

非常感谢!!

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

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

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

4 回答 4

5

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

我查看了 Lunar 网页,在我看来,好像您需要methods在类型上创建一个表T,然后调用该Luna<T>::Register方法。网上有一个简单的例子。如果我正确阅读了代码,那么您问题中的任何胶水代码实际上都不是使用 Lunar 做事的推荐方式。(我还假设您可以将这些方法完全实现为 C++ 调用。)

这一切都很狡猾,因为 Lunar 上的文档很薄。一个明智的选择是自己完成所有工作,只需将每个 C++ 类型与包含其方法的 Lua 表相关联。然后你让 Lua__index元方法查询该表,并且 Bob 是你的叔叔。Lunar 正在做一些接近这些的事情,但它已经充分地装饰了 C++ 模板,而我不知道如何使它工作。

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

总结:对每个类,做一个显式的方法表,并使用 LunarRegister方法注册每个类。或者自己滚动。

于 2009-05-15T00:14:55.890 回答
1

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

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

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

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

如果您对 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 中,通过 metatable 存储方法表:

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

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

dispatch[getmetatable(rock)](robot, rock)
于 2009-05-15T00:01:31.180 回答
1

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

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

但是,在 C++ 中,创建一个具有 .invoke 方法的 LuaClass,接受一个 C 字符串(即一个以 null 结尾的 const char 数组)来指定您要调用的成员函数的名称,具体取决于您要如何调用处理可变参数,有这个 .invoke 方法的多个模板版本,用于零、一、二、... .

对于 Lua,我建议创建两个 .invoke 方法,一个需要 std::vector,另一个需要 std::map,但我会留给你。:)

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

享受。

于 2009-05-15T00:29:02.533 回答
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 类构建每个代理的一个实例。即:在 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)

对“函数”方法/代理执行相同的操作。

现在 Lunar 标头有一些小的变化:

从类中删除“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<> 。并添加一些“中间”抽象类,带有很好的虚拟方法,例如:

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

... 我希望这个能帮上忙。不要犹豫,纠正我/添加东西/反馈。

干杯:)

于 2013-04-09T13:28:32.517 回答