Lua, С++ и подклассы бедных

Я возглавляю 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 )

Ответ 1

Вы должны сообщить нам, что именно не работает в вашем коде. Я полагаю, что это Lunar<Rock>::check(L, 1) не подходит для всех не-Rocks. Правильно ли я?

Также было бы хорошо, если бы вы указали, какую версию Lunar вы используете (ссылка на нее была бы большой).

Если это этот, то тип класса сохраняется в объекте Lua metatable (можно сказать, что этот метатебель является типом),

Похоже, что самый простой способ проверить, является ли объект Rock без исправления Lunar, - это вызвать luaL_getmetatable(L, Rock::className), чтобы получить класс metatable и сравнить его с lua_getmetatable (L, 1) вашего первого аргумента (примечание lua L в первом имени функции). Это немного хакерское, но должно работать.

Если вы воспользуетесь патчей Lunar, одним из возможных способов является добавление некоторого поля __lunarClassName в metatable и сохранение T::name там. Предоставьте lunar_typename() функцию С++ (вне класса Lunar template, так как нам там не нужен T), а затем возвращаем из нее значение этого поля аргумента __lunarClassName. (Не забудьте проверить, имеет ли объект metatable, и что метатабельное имеет такое поле.) Вы можете проверить тип объекта Lua, вызывая lunar_typename() тогда.

Немного советов из личного опыта: чем больше бизнес-логики вы нажимаете на Lua, тем лучше. Если вам не нужны жесткие ограничения производительности, вероятно, вам стоит подумать о том, чтобы переместить всю эту иерархию в Lua - ваша жизнь стала бы намного проще.

Если я могу помочь вам, скажите об этом.

Обновление: Решение, в котором вы обновили сообщение, выглядит правильно.

Чтобы выполнить отправку на основе метаданных в C, вы можете использовать, например, карту интегрального значения lua_topointer() значения luaL_getmetatable() для типа к объекту/указателю функции, который знает, как справиться с этим типом.

Но, опять же, я предлагаю перенести эту часть на Lua. Например: экспортировать типы специфических функций getFiringSolutionForRock(), getFiringSolutionForStone() и getFiringSolutionForRockyStone() из С++ в Lua. В Lua сохраните таблицу методов по метатебельному:

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

Если я прав, следующая строка должна вызывать правильный специализированный метод объекта робота.

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

Ответ 2

Я думаю, вы пытаетесь отправить метод в неправильном месте. (Эта проблема является симптомом сложности со всеми этими "автоматизированными" способами взаимодействия Lua с C или С++: с каждым из них происходит некоторая магия за кулисами, и не всегда очевидно, как заставить ее работать. Я не понимаю, почему больше людей не просто используют Lua C API.)

Я просмотрел веб-страницы Lunar, и мне кажется, что вам нужно создать таблицу methods по типу T, а затем вызвать метод Luna<T>::Register. Там простой пример в Интернете. Если я правильно читаю код, ни один из кода клей в вашем вопросе на самом деле не рекомендуется делать с Лунным. (Я также предполагаю, что вы можете реализовать эти методы полностью как вызовы С++.)

Это все довольно хитроумно, потому что документация на Лунной тонкая. Разумной альтернативой было бы сделать всю работу самостоятельно и просто связать каждый тип С++ с таблицей Lua, содержащей ее методы. Тогда у вас есть метаметод Lua __index, который проконсультируется с этой таблицей, а Боб - ваш дядя. Лунный делает что-то близко к ним, но он достаточно одет в С++-шаблоны, которые другие goo, что я не уверен, как заставить его работать.

Материал шаблона очень умный. Возможно, вы захотите либо потратить время, чтобы глубоко понять, как это работает, либо пересмотреть, если и как вы хотите его использовать.

Сводка: для каждого класса создайте таблицу явных методов и зарегистрируйте каждый класс с помощью метода Lunar Register. Или сверните свой собственный.

Ответ 3

Я предлагаю вам определить объектно-ориентированную систему в чистом lua, а затем написать пользовательскую привязку к С++ для этого аспекта API.

Lua хорошо подходит для прототипов OO-реализаций, где таблицы используются для эмуляции классов, в которых одна запись имеет функцию new, которая при вызове возвращает соответствующую таблицу того же типа.

Из С++, однако, создайте LuaClass, который имеет метод .invoke, принимающий строку C (т.е. массив const char с нулевым завершением), чтобы указать имя функции-члена, которую вы хотите вызвать, и в зависимости от о том, как вы хотите обрабатывать переменные аргументы, имеете несколько шаблонных версий этого метода .invoke для нулевого, одного, двух,... N аргументов как необходимых или определить метод передачи в него переменного количества аргументов, и есть многие способы сделать это.

Для Lua я предлагаю сделать два метода .invoke, один из которых ожидает std::vector, а другой, который ожидает std:: map, но я оставлю это до вас.:)

В моем последнем проекте Lua/С++ я использовал только массивы с C-строками с нулевым завершением, требуя, чтобы lua преобразовал строку в соответствующее значение.

Enjoy.

Ответ 4

Я столкнулся с такими же потребностями, и вот что я придумал. (Мне пришлось сделать некоторые незначительные изменения в лунном заголовке)

Во-первых, я добавил глобальный "интерфейс" для всех классов, которые будут содержать методы 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, после стандартного объявления метода "Лунный":

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;

(обратите внимание, что вам также нужно добавить некоторый static_cast внутри класса Lunar)

Хорошо, хорошо. Теперь у нас есть все структуры, необходимые для выполнения нашей операции, я определил ее в методе getStackObject() моего LuaAdapter на основе вашего кода.

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> 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;
}

... Надеюсь, это поможет. Не стесняйтесь корректировать меня/добавлять материал/отзывы.

Приветствия:)