新しい型のバインディング
内部をバインドせずにドキュメント生成器へ新しい型を追加する
C++ の型がドキュメント生成器に登録されていない場合、<cppval: **gibberish** > として表示されます。この問題を緩和するには、catalua_luna_doc.h に LUNA_VAL( your_type, "YourType" ) を追加します。生成器は引数の型に YourType という文字列を使用します。
Lua に新しい型をバインドする
まず、バインディングシステムに新しい型を登録する必要があります。ドキュメント生成器が型を理解できるようにし、ランタイムがその型を含む Lua テーブルを JSON からデシリアライズできるようにするなど、多くの理由で必要です。登録しないと Type must implement luna_traits<T> というコンパイルエラーが発生します。
-
catala_luna_doc.hに型の宣言を追加します。例えば、架空のhorde型(struct)をバインドする場合、ファイルの上部付近に1行追加します:struct horde;複雑なテンプレート型では関連ヘッダーを実際に読み込む必要がある場合がありますが、コンパイル時間に大きく影響するため、できるだけ避けてください。
-
同じファイルで型をドキュメント生成器に登録します。
hordeの例を続けると、次のようにします:LUNA_VAL( horde, "Horde" );C++ の型名にはさまざまなスタイルがありますが、Lua 側ではすべて
CamelCaseにします。
ここから実際の詳細に入ります。バインディングは catalua_bindings*.cpp ファイルに実装されています。コンパイルを高速化し、見つけやすくするため複数の .cpp ファイルに分かれているので、既存の catalua_bindings*.cpp に追加するか、同様のファイルを作成できます。同じ理由で関数にも分かれています。horde 型を登録し、新しいファイルと関数に入れてみましょう:
catalua_bindings.hに新しい関数宣言を追加します:void reg_horde( sol::state &lua );catalua_bindings.cppのreg_all_bindingsで関数を呼び出します:reg_horde( lua );- 次の内容で
catalua_bindings_horde.cppという新しいファイルを作成します:#include "catalua_bindings.h" #include "horde.h" // Replace with the header where your type is defined void cata::detail::reg_horde( sol::state &lua ) { sol::usertype<horde> ut = luna::new_usertype<horde>( lua, luna::no_bases, luna::constructors < // Define your actual constructors here horde(), horde( const point & ), horde( int, int ) > () ); // Register all needed members luna::set( ut, "pos", &horde::pos ); luna::set( ut, "size", &horde::size ); // Register all needed methods luna::set_fx( ut, "move_to", &horde::move_to ); luna::set_fx( ut, "update", &horde::update ); luna::set_fx( ut, "get_printable_name", &horde::get_printable_name ); // Add (de-)serialization functions so we can carry // our horde over the save/load boundary reg_serde_functions( ut ); // Add more stuff like arithmetic operators, to_string operator, etc. } - これで完了です。型は Lua から
Hordeという名前で見えるようになり、バインドしたメソッドとメンバーを使用できます。
Lua に新しい型をバインドする(Neovim の正規表現を使用)
クラスや構造体を手作業で Lua にバインドするのはかなり面倒なので、ヘッダーファイルを変換してクラスをバインドする別の方法があります。前述の第2部の第3ステップでは、Neovim の組み込み正規表現と C++ マクロを使ってクラスをバインドできます。
- クラス定義のコピーを作成します。
- 次の両方を適用します:
%s@class \([^{]\)\+\n*{@private:@%s@struct \([^{]\)\+\n*{@public:@ - 冒頭にあるコンストラクターや不要なメソッドを手動で削除します。
- すべての
private/protectedメソッドを削除します:%s@\(private:\|protected:\)\_.\{-}\(public:\|};\)@\2 - クラス定義末尾の
};を削除します。 publicラベルを削除します:%s@ *public:\n@- コメントを削除します:
%s@\( *\/\*\_.\{-}\*\/\n\{,1\}\)\|\( *\/\/\_.\{-}\(\n\)\)@\3@g - 基本インデントが0になるまでインデントを解除します。
- メソッド定義を宣言に変換します:
%s@ *{\(}\|\_.\{-}\n^}\)@; - ほとんどのメソッド宣言を1行にまとめます:
%s@\((\|,\)\n *@\1@g - デフォルト値を削除します:
%s@ *= *\_.\{-}\( )\|;\|,\)@\1@g overriden/staticメソッド・メンバーとusingを削除します:%s@.*\(override\|static\|using\).*\n@@gtemplateを削除します:%s@^template<.*>\n.*\n@@gvirtualタグを削除します:%s@^virtual *@- すべての行がセミコロンで終わっているか確認します:
%s@\([^;]\)\n@\0@gn - 関数の数を数えます:
%s@\(.*(.*).*\)@@nc - 最初に見つかった関数を末尾へ移動します:
%s@\(.*(.*).*\)\n\(\n*\)\(\_.*\)@\3\1\2 - 次に、15のステップで見つかった数から1を引いた回数だけ、16のステップを繰り返します。Neovim では、マッチ数から1を引いた値、
@、それから:を入力します。例えば'217@:'なら直前のコマンドを217回繰り返します。 - 改行を整理します:
%s@\n\{3,}@\r\r - メソッドをマクロでラップします:
%s@\(.*\) \+\([^ ]\+\)\((.*\);@SET_FX_T( \2, \1\3 ); - メンバーをマクロでラップします。先に対象範囲を選択してください:
s@.\{-}\([^ ]\+\);@SET_MEMB( \1 ); - 以前複数行だったメソッド宣言を再び複数行にします:
%s@\(,\)\([^ ]\)@\1\r \2@g
残るのは、テキストのまとまりを Lua バインディングに使用することです。horde の例を続けると、これらのマクロを使ったコードは次のようになります:
#include "catalua_bindings.h"
#include "catalua_bindings_utils.h"
#include "horde.h" // Replace with the header where your type is defined
void cata::detail::reg_horde( sol::state &lua )
{
#define UT_TYPE horde
sol::usertype<UT_TYPE> ut =
luna::new_usertype<UT_TYPE>(
lua,
luna::no_bases,
luna::constructors <
// Define your actual constructors here
UT_TYPE(),
UT_TYPE( const point & ),
UT_TYPE( int, int )
> ()
);
// Register all needed members
SET_MEMB( pos );
SET_MEMB( size );
// Register all needed methods
SET_FX_T( move_to, ... ); // Instead of ..., there'd be the type declaration of the method.
SET_FX_T( update, ... );
SET_FX_T( get_printable_name, ... );
// Add (de-)serialization functions so we can carry
// our horde over the save/load boundary
reg_serde_functions( ut );
// Add more stuff like arithmetic operators, to_string operator, etc.
// ...
#undef UT_TYPE // #define UT_TYPE horde
}
この方法にはテンプレートメソッドのバインディングがなく、壊れる可能性があります。コンパイラーエラーやリンカーのフリーズが起きるため、基本的に壊れているものと考え、わずかな修正や手動追加が必要になると想定するのがよいでしょう。
Lua に新しい enum をバインドする
enum のバインディングは型のバインディングと似ています。架空の horde_type enum をバインドしてみましょう:
- enum に明示的な基底型(ヘッダーで
enum nameの後にある: type部分)がない場合は、まず基底型を指定します。例:// hordes.h - enum class horde_type { + enum class horde_type : int { animals, robots, zombies } catalua_luna_doc.hに宣言を追加します:enum horde_type : int;catalua_luna_doc.hで次のように登録します:LUNA_ENUM( horde_type, "HordeType" )- enum が
std::stringとの自動変換を実装していることを確認します。詳細はenum_conversions.hを参照してください。すでに実装されている enum もありますが、多くは実装されていません。通常はヘッダーで enumTのenum_traits<T>を特殊化し、.cppファイルで enum から文字列への変換を使うio::enum_to_string<T>を定義します。enum_traits<T>に必要な「最後」の値を持たない enum もあります。その場合は追加する必要があります:
これは「単調な」enum、つまり0から始まり値を飛ばさないものにだけ機能します。上の例ではenum class horde_type : int { animals, robots, - zombies + zombies, + num_horde_types }animalsは暗黙的に0、robotsは1、zombiesは2なので、正しく期待どおりの暗黙値3を持つnum_horde_typesを簡単に追加できます。 catalua_bindings.cppのreg_enums関数で enum のフィールドをバインドします:
これはステップ4の自動変換を使うため、JSON と Lua で同じ名前になります。reg_enum<horde_type>( lua );
新しい string_id<T> または int_id<T> を Lua にバインドする
これらは T 自体のバインディングとは別に行えます。
- まだ登録していなければ、型
Tをドキュメント生成器に登録します(関連ドキュメントを参照)。 - ステップ1の
LUNA_VALをLUNA_IDに置き換えます。 - 型
Tが演算子<と==を実装していることを確認します。通常は手動で実装しやすく、catalua_type_operators.hのLUA_TYPE_OPSマクロで半自動的に実装できます。 - 型
Tに nullstring_idがあることを確認します。なければstring_id_null_ids.cppに追加できます。Tが class として定義されている場合はMAKE_CLASS_NULL_IDマクロ、それ以外の場合はMAKE_STRUCT_NULL_IDマクロを使います。 - 型
Tのstring_idにobj()とis_valid()メソッドが実装されていることを確認します。これらは型ごとに実装されています。他のstring_idを例として確認することを推奨します。 - 該当する
catalua_bindings_ids_*.cppのまとまりに、型 T が定義されているヘッダーを追加します:#include "your_type_definition.h" - そのまとまりの
reg_game_ids_*関数で次のように登録します:reg_id<T, true>( lua );
string_id<T> だけをバインドし、int_id<T> を必要としない(または実装できない)場合は、この true を false に置き換えられます。
この段階で、例えば is_valid() や NULL_ID() メソッドについてリンカーエラーが出ることがあります。さまざまな理由で、すべての string または int id にこれらが実装されているわけではないためです。その場合は手動で定義してください。詳しくは string_id と int_id の関連ドキュメントを参照してください。
これで完了です。型 T は Lua では Raw 接尾辞付きで表示され、string_id<T> には Id、int_id<T> には IntId 接尾辞が付きます。例えば LUNA_ID( horde, "Horde" ) では次のようになります:
horde->HordeRawstring_id<horde>->HordeIdint_id<horde>->HordeIntId
3つの間の型変換はすべてシステムによって自動実装されます。T の実際のフィールドとメソッドは、通常どおり Lua にバインドできます。