Lua, C ++ и подклассы бедняков - PullRequest
11 голосов
/ 15 мая 2009

Я ведущий разработчик для Bitfighter , и мы работаем со смесью Lua и C ++, используя Lunar (вариант Luna, доступен здесь ), чтобы связать их все вместе.

Я знаю, что в этой среде нет хорошей поддержки ориентации и наследования объектов, но я бы хотел найти способ хотя бы частично обойти эти ограничения.

Вот что у меня есть:

Структура класса C ++

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

    Robot

Робот реализует метод под названием getFiringSolution (элемент GameItem) , который смотрит на положение и скорость item и возвращает угол, под которым роботу нужно будет стрелять, чтобы ударить пункт .

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

Так что моя проблема в том, что я хочу передать камни , камни или 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));    
}

Это потенциальное решение не работает, потому что функция проверки Лунара хочет, чтобы объект в стеке имел className, совпадающее с определенным для 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));    
}

Проблема с этим решением состоит в том, что функция 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, который передается обратно в метод 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 ]

5 голосов
/ 15 мая 2009

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

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

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

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

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

1 голос
/ 15 мая 2009

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

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

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

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

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

Наслаждайтесь.

1 голос
/ 15 мая 2009

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

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

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

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

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

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

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

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

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

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

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

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

dispatch[getmetatable(rock)](robot, rock)
0 голосов
/ 09 апреля 2013

Я столкнулся с точно такими же потребностями, и вот что я придумал. (Мне пришлось сделать небольшие изменения в заголовке 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;

(обратите внимание, что вам также нужно добавить немного 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>'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;
}

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...