11

私はBitfighterの主任開発者であり、Luna (Luna のバリアントで、こちらから入手可能) を使用して Lua と C++ を組み合わせて作業しています。

この環境ではオブジェクト指向と継承が適切にサポートされていないことはわかっていますが、これらの制限を少なくとも部分的に回避する方法を見つけたいと考えています。

ここに私が持っているものがあります:

C++ クラス構造

    ゲームアイテム
       |---- ロック
       |---- ストーン
       |---- ロッキーストーン

    ロボット

Robot はgetFiringSolution(GameItem item)というメソッドを実装します。このメソッドはitemの位置と速度を調べ、ロボットがitemを攻撃するために発砲する必要がある角度を返します。

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

したがって、私の問題は、rocksstone、または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 に定義されたものと一致する 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 かどうかを確認します。もしそうなら、すべてが桃色であり、getFiringSolution() メソッドに渡される私の Rock オブジェクトへのポインターを返します。スタックに 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>'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));    
}

おそらくこれでさらに最適化を行うことができます...これをループに折りたたむ方法を本当に知りたいです。実際には、処理するクラスが 3 つよりもはるかに多いため、このプロセスは次のとおりです。少し面倒です。

上記のソリューションのわずかな改善

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のCAPIを使用しないのかわかりません。)

LunarのWebページを見てみると、methodstypeでテーブルを作成してから、メソッドTを呼び出す必要があるように見えます。Web上に簡単な例がありLuna<T>::Registerます。私がコードを正しく読んでいる場合、あなたの質問のグルーコードは実際にはLunarで物事を行うための推奨される方法ではありません。(また、これらのメソッドを完全にC ++呼び出しとして実装できると想定しています。)

Lunarのドキュメントが薄いため、これはすべてかなり危険です。賢明な代替策は、すべての作業を自分で行い、各C++タイプをそのメソッドを含むLuaテーブルに関連付けることです。次に、Luaメタメソッドに__indexそのテーブルを参照させ、Bobがあなたの叔父です。Lunarはこれらに近いことをしていますが、C ++テンプレートで十分にドレスアップされているので、他のgooがそれを機能させる方法がわかりません。

テンプレートのものは非常に賢いです。時間をかけてそれがどのように機能するかを深く理解するか、それを使用するかどうか、どのように使用するかを再検討することをお勧めします。

概要:クラスごとに、明示的なメソッドテーブルを作成し、LunarRegisterメソッドを使用して各クラスを登録します。またはあなた自身を転がしてください。

于 2009-05-15T00:14:55.890 に答える
1

コードで正確に機能しないものを教えてください。Lunar<Rock>::check(L, 1)Rocks以外のすべての人にとって失敗するのはそれだと思います。私は正しいですか?

また、使用するLunarのバージョンを指定しておけば問題ありません(リンクがあれば便利です)。

これがこれである場合、クラスタイプはLuaオブジェクトメタテーブルに格納されます(このメタテーブルタイプであると言うことができます)。

Lunarにパッチを適用せずにオブジェクトがRockであるかどうかを確認する最も簡単な方法は、luaL_getmetatable(L, Rock::className)クラスmetatableを取得し、それを最初の引数のlua_getmetatable(L、1)と比較することです(最初の関数名のlua Lに注意してください)。これは少しハックですが、動作するはずです。

Lunarにパッチを適用することに問題がない場合、考えられる方法の1つは__lunarClassName、メタテーブルにフィールドを追加してT::nameそこに保存することです。lunar_typename()次に、 C ++関数(Lunarテンプレートクラスの外部-必要ないため)を提供し、そこから引数のメタテーブルのフィールドのT値を返します。__lunarClassName(オブジェクトにメタテーブルがあり、そのメタテーブルにそのようなフィールドがあるかどうかを確認することを忘れないでください。)lunar_typename()次に呼び出すことでLuaオブジェクトタイプを確認できます。

個人的な経験からのちょっとしたアドバイス:Luaにプッシュするビジネスロジックが多ければ多いほど良いです。厳しいパフォーマンスの制約に悩まされていない限り、おそらくそのすべての階層をLuaに移動することを検討する必要があります-あなたの人生ははるかに簡単になります。

さらにお手伝いさせていただく場合は、その旨をお伝えください。

更新:投稿を更新したソリューションは正しいようです。

Cでメタテーブルベースのディスパッチを実行するには、たとえば、型の整数lua_topointer()値の、luaL_getmetatable()その型の処理方法を知っている関数オブジェクト/ポインターへのマップを使用できます。

しかし、繰り返しになりますが、代わりにこの部分をLuaに移動することをお勧めします。例:タイプ固有の関数getFiringSolutionForRock()をエクスポートgetFiringSolutionForStone()getFiringSolutionForRockyStone()、C++からLuaにエクスポートします。Luaでは、メソッドのテーブルをメタテーブルで保存します。

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 の実装に適しています。そこではテーブルがクラスをエミュレートするために使用され、1 つのエントリに new と呼ばれる関数があり、呼び出されると同じ「タイプ」の適切なテーブルが返されます。

ただし、C++ からは、.invoke メソッドを持つ LuaClass を作成し、C 文字列 (つまり、null で終わる const char 配列) を受け入れて、呼び出したいメンバー関数の名前を指定します。可変引数を処理し、必要に応じてゼロ、1、2、... N 個の引数に対してこの .invoke メソッドのテンプレート化されたバージョンをいくつか用意するか、可変数の引数を渡すメソッドを定義します。これを行うには多くの方法があります。 .

Lua の場合、2 つの .invoke メソッドを作成することをお勧めします。1 つは std::vector を想定し、もう 1 つは std::map を想定していますが、それはあなたに任せます。:)

前回の Lua/C++ プロジェクトでは、null で終わる C 文字列の配列のみを使用し、lua で文字列を適切な値に変換する必要がありました。

楽しみ。

于 2009-05-15T00:29:02.533 に答える
0

私はまったく同じニーズに直面していましたが、これが私が思いついたものです. (Lunar ヘッダーに若干の変更を加える必要がありました)

まず、Lua メソッドを含むすべてのクラスにグローバルな「インターフェース」を追加しました。これは「元の」方法よりも柔軟性が低いように見える可能性があることは理解していますが、私の意見では、より明確であり、動的キャストを実行するために必要です。

class LuaInterface
{
  public:
    virtual const char* getClassName() const=0;
};

はい、純粋な仮想メソッドが 1 つだけ含まれており、派生クラスで静的な "className" 属性を明らかに返します。そうすれば、テンプレート化された lunar クラスが必要とするこの静的な名前のメンバーを維持しながら、ポリモーフィズムを実現できます。

私の人生を楽にするために、いくつかの定義も追加しました:

#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 クラスごとに、各プロキシのインスタンスを 1 つ作成する必要があります。つまり、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)

「関数」メソッド/プロキシで同じことを行います。

月のヘッダーにいくつかの小さな変更が加えられました。

クラスから「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 に答える