Я возглавляю dev для Bitfighter, и мы работаем с сочетанием Lua и С++, используя Lunar (вариант Luna, доступно здесь), чтобы связать их вместе.
Я знаю, что эта среда не имеет хорошей поддержки ориентации объектов и наследования, но я хотел бы найти способ хотя бы частично обойти эти ограничения.
Вот что у меня есть:
Структура класса С++
GameItem |---- Rock |---- Stone |---- RockyStone Robot
Robot реализует метод под названием getFiringSolution (элемент GameItem), который смотрит на положение и скорость item и возвращает угол, в который роботу нужно будет стрелять 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));
}
Это потенциальное решение не работает, потому что функция проверки луны хочет, чтобы объект в стеку имел имя класса, которое соответствует определению, определенному для 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, который возвращается к методу 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> 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));
}
Есть, возможно, дальнейшие оптимизации, которые я могу сделать с этим... Мне бы очень хотелось разобраться, как свернуть это в цикл, потому что на самом деле у меня будет более чем три класса, с которыми нужно иметь дело, и этот процесс немного громоздкий.
Незначительное улучшение вышеприведенного решения
С++:
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 )