我是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 )
答案 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;
}
......我希望这会有所帮助。不要犹豫,纠正我/添加内容/反馈。
干杯:)