Lua API ++

こんにちは同僚。

私の小さなプロジェクトを紹介したいと思います。これはあなたに役立つと思います。



数年前、適度なサイズと高いパフォーマンスを備えた組み込みスクリプト言語を探していたときに、Luaに会いました。 Luaはこれらのニーズを満たすだけでなく、その驚くべきシンプルさと表現力にも魅了されます。



Lua APIに不満があるとは言えません。これは便利で使いやすい優れた機能セットです。 言語をアプリケーションに統合し、独自の拡張機能を追加しても問題は発生せず、「落とし穴」も発生しませんでした。 しかし、それでも、このC指向のAPIを使用するとき、このプロセスがより便利になる可能性があるという考えが残っていませんでした。 便利なオブジェクト指向のラッパーを作成する最初の試みは失敗しました。利用可能な資金では、私は存在するに値するものを作成できませんでした。



そして、 C ++ 11が登場し 、私を妨げるすべての障害が取り除かれ(より正確に-不足しているものが追加されました)、パズルが徐々に形になり始めました。 2番目のアプローチは成功し、その結果、ほとんどの操作の自然な構文でかなり軽量なラッパーライブラリを作成することができました。 私がLua API ++と呼んだこのライブラリは、 Lua APIの便利な代替品として機能することを目的としています。 Lua Workshopでの私のプレゼンテーションに基づいたこの記事は、Lua API ++とその機能の基本概念を理解するのに役立ちます。






主役



知り合いは、ライブラリで使用される基本概念と、それらの関係から始める必要があります。 予想どおり、これらの概念は対応するタイプに反映されています。



State



State



はLua州の所有者です。 これは独立したタイプであり、他のライブラリとは実質的に無関係です。 状態の作成と破棄の制御に加えて、ファイル、文字列、およびLua互換機能を実行する手段のみを提供します。 操作中に発生したエラーは例外に変換されます。



LFunction



ライブラリ内の他のすべては、 LFunction



、Lua API ++と互換性のある特別な形式の関数内で発生します。 これはLua互換機能に類似しており、 CFunction



という名前が付けられていCFunction



。 主に次の文字を入れるために、特別な関数形式が必要でした:



Context



Context



は、関数のコンテキストであり、Luaのすべての機能へのアクセスセンターです。 これを使用して、グローバル変数、関数の引数、 レジストリ、およびupvaluesにアクセスできます。 ガベージコレクタを管理し、エラーを通知し、複数の戻り値を渡し、障害を作成できます。 簡単に言えば、 Context



を通じて、トレーリングの特権である値の操作に直接適用されないすべてが行われます。



価値
同じ名前のクラスが明確に対応していた以前の概念とは異なり、Lua API ++の「値」はやや曖昧です(もちろん、 Value



クラスは存在します)。 これは主に、「オープンボーダー」ポリシーによるもので、Luaでネイティブ値を自由に移行でき、その逆も可能です。 Luaの値が予想される場合はいつでも、サポートされている型のネイティブ値を置き換えることができ、それらは自動的にLuaスタックに「移動」します。 暗黙の型変換演算子は、値を反対方向に移動するのに役立ちます。実際の型と期待される型に互換性がない場合は、例外でこれを通知します。

さらに、Luaの値は、その起源に応じて、共通のインターフェースをサポートするさまざまなタイプで表すことができます。 このインターフェイスは、値に対するすべての有効な操作を実装します。ネイティブ型への明示的および暗黙的な変換、関数呼び出し、インデックス付け、算術演算、比較、型チェック、メタテーブルの書き込みおよび読み取り。



Valref



これは、スタックに配置された値へのリンクであり、むしろ、Luaスタックの特定のスロットほどではありません。 Valref



は、スタック上の値の配置または削除をValref



しませんが、値の操作のみに焦点を合わせます。 Lua API ++ Valref



は、値を表す他のタイプのインターフェースに従うモデルとして機能します。



一時的な
操作の結果である一時的な値では、やや複雑です。 これらは、操作の結果としてスタックにプッシュされる (またはプッシュされない)値であり、 一度使用されてから削除されます。 さらに、操作自体の引数は他の操作の結果である場合があり、成功を保証するものではありません。 また、使用方法が異なる場合があります。スタックでの読み取りの結果としてインデックスを作成すると、キーの代わりに新しい値が作成され、書き込みの結果として、キーと記録された値がスタックから削除されます。 操作の引数の配置順序を厳密に監視する必要性についてはどうですか? そして、未使用のオブジェクトをどうするか?

多くの人は、おそらく私が何を得ているかをすでに推測しているでしょう。 一時的な値はプロキシタイプによって表されます。 これらは、ユーザーが目に見えないようにテンプレートで設計し、 Valref



インターフェースを再現します。 それらの使用は簡単、簡単、便利ですが、間違いを犯すと、コンパイラーは山括弧でいっぱいの膨大な構成で「喜んで」くれます。



アンカー
アンカーには、1つ以上の値をスタックに「固定」できるため、名前が付けられています。 Value



は単一の値の汎用的な「アンカー」であり、 Table



テーブル専用であり、 Valset



は複数の値を格納します。






メインキャラクターが表示されたので、それらを使用して何ができるかについて、より詳細な分析を開始できます。



State



State



は、コンテキストの初期化に必要なすべてのアクションを実行するデフォルトのコンストラクターがあります。 別のコンストラクターを使用すると、 カスタムメモリ管理関数を使用できます。 getRawState



関数により、Lua APIで使用される状態オブジェクトへの生のポインターを要求できます。

このセットには、関数runFile



runString



、およびcall



含まれており、簡単なインタープリターを作成できます。



最も単純なインタープリター
 #include <iostream> #include <luapp/lua.hpp> using namespace std; using namespace lua; void interpretLine(State& state, const string& line) { try { state.runString(line); //    } catch(std::exception& e) { //          cerr << e.what() << endl; } } void interpretStream(State& state, istream& in) { string currentLine; while(!in.eof()) { //        getline(in, currentLine); interpretLine(state, currentLine); } } int main() { State state; interpretStream(state, cin); }
      
      










エラー処理



ライブラリで使用されるアプローチは、Luaの足元に収まることではないため、 Table



からではなくTable



を作成しようとするなど、ライブラリ自体に関連するエラーが診断されるか、ユーザーコードで(おそらく)インターセプトする必要があるエラーが診断されます、型キャストエラーのように。 ライブラリは、Lua APIの呼び出し時に検出される可能性のあるエラーを事前に診断しようとしません。 したがって、たとえば、実際に数値である値に対して関数呼び出しを使用しようとしても、例外は発生しません。 lua_call



呼び出し内で検出され、Luaスタイルのエラー(実行の中断と保護された呼び出しの最も近いポイントに戻る)が発生します。






LFunction





実際、ライブラリは、サポートされている型(およびメンバー関数)で動作する関数の「透明な」ラッパーをサポートしています。 Lua値が期待される関数の名前を単に述べてください。 しかし、Lua API ++が提供するすべてのLuaアメニティにアクセスする場合は、このプロトタイプに従ってL関数を記述する必要があります。
 Retval myFunc(Context& c);
      
      





ここではすべてが簡単です。関数はContext



を受け取り、 Retval



Context::ret



関数を介して任意の数の値を簡単に返すのに役立つ特別な型です。



mkcf



テンプレートを使用すると、 LFunction



Luaを友達にできます。
 int (*myCfunc)(lua_State*) = mkcf<myFunc>;
      
      





このようにして、関数のラッパーを明示的に作成できます。 「透明な」ラッパーも機能しますが、オーバーヘッドはわずかに高くなります。 一方、 mkcf



はそれぞれの場合に個別のラッパー関数を作成します。

いずれにしても、「ラッパー」はContext



オブジェクトを作成して関数に渡し、作業が完了すると、 Retval



を通じてRetval



れた値をLuaに渡します。 ラップされた関数のスコープを超えるすべての例外はキャッチされ、Luaエラーに変換されます。

関数はそれ自体を返しますか? ちょうだい!
 Retval retSelf(Context& c) { return c.ret(retSelf, mkcf<retSelf>); //    ,  - }
      
      










Context





関数コンテキストは、Luaへの中央アクセスノードです。 値の操作に直接関係しないすべては、 Context



介して行われます。 私はgod objectに明確に似ているというヒントを拒否しませんが、この場合、そのような決定はLua APIのアーキテクチャによって決定されます。 Context



介して、ガベージコレクターを制御し、スタックに配置されたバージョン番号と値の数を確認できます。 Lua APIを直接使用する必要がある場合に備えて、暗黙的にlua_State*



に変換されます。 この場合、マジックLFunction



(より正確には、信号タイプの静的定数)がinitializeExplicitly



initializeExplicitly



。これはinitializeExplicitly



。これにより、 LFunction



外部でContext



明示的に作成できます。



戻り値
returnステートメントで関数から返された値を単純に示すのがどんなに素晴らしいとしても、これは不可能です。 2つの最も近い選択肢から選択する必要がありました。カンマがオーバーロードされた演算子または関数呼び出しを使用したcな「スターター」です。 フレンドシップ機能が勝ちました。 したがって、 LFunction



LFunction



必要があります。これは、控えめな名前ret



Context



メソッドにアクセスすることによってのみ作成できます。 これは特別な関数です。呼び出された後、スタックからの値を捨てないようにスタックを停止します。したがって、 returnステートメントでのみ直接使用する必要があります。 ret



呼び出しでは、必要な数の戻り値をリストできます。

比較
 return ctx.ret(1, "two", three);
      
      





同等のコード:

 lua_pushinteger(ctx, 1); lua_pushstring(ctx, "two"); lua_pushvalue(ctx, three); return 3;
      
      









エラー報告
Retval



を作成する唯一の方法はret



関数を使用することであると主張して、私は真実に罪を犯していませんが、1つの警告があります...正式な観点から、このタイプを返すerror



関数もあります。 実際、 Retval



が作成に到達しないのは、この関数からのリターンがないためです。 期待できる最大値は、メッセージをLuaエラー処理エンジンに渡すことです。 Lua APIドキュメントでは、 returnステートメントlua_error



呼び出しを使用して、呼び出し中に関数が中断されることを示すことを推奨しています。 同じアプローチがLua API ++でも使用されているため、 error



Retval



を返すerror



宣言されていRetval





エラーメッセージを含むLua値が引数として使用され、特にwhere



関数が文字列を作成して現在の関数を説明する行を作成できるため、ここでの連結は非常に適切です。 メッセージがまったく指定されていない場合、同じ値が使用されます。

 if(verbose) return ctx.error(ctx.where() & " intentional error " & 42); else return ctx.error(); //   ,  return ctx.error(ctx.where());
      
      





同等のコード
 if(verbose) { luaL_where(ctx, 0); lua_pushstring(ctx, " intentional error "); lua_pushinteger(ctx, 42); lua_concat(ctx, 3); return lua_error(ctx); } else { luaL_where(ctx, 0); return lua_error(ctx); }
      
      







環境へのアクセス
私たちのContext



は明らかに価値の主な源です。 実際、彼らはどこから来たのでしょうか?

Context



クラスのパブリックメンバーとして設計されたアクセスオブジェクトが提供されているため、環境のさまざまな興味深い場所にアクセスできます。 それらはすべて、値の読み取りと書き込みを許可します。



まず第一に、これらargs



、関数の引数です。 ユーザーがアクセスできない特別なタイプが作成された他のアクセスオブジェクトとは異なり、通常の定数Valset



がここで使用されます。 その不変性とは、サイズを変更できないことだけを意味しますが、引数の意味を書き換えることは健康のためです。 ValsetはSTL互換のコンテナとして作成されたため、その中の要素の番号付けは0から始まります。他の場合、ライブラリはLuaの規則に従い、インデックス付けが1から始まることを意味します。

 if(ctx.args.size() > 1 && ctx.args[0].is<string>()) {...};
      
      





同等のコード
  if(nArgs > 1 && lua_isstring(ctx, 1) ) {...};
      
      





2番目は、グローバル変数へのアクセスです。 global



オブジェクトは、文字列によってインデックス付けされます。

 ctx.global["answer"] = 42; //      ,  
      
      





同等のコード
 lua_pushinteger(ctx, 42); lua_setglobal(ctx, "answer");
      
      





LFunctionもクロージャである場合、整数インデックス(1から始まるすべての値が正しい)を使用して、値に格納されている値にアクセスできます。 格納されている値の数を知る方法はありません。これはすでにわかっていると想定されています。



レジストリからアクセスできるLuaレジストリは、2つの方法で使用されます。 文字列キーにより、ユーザーデータのメタテーブルがそこに格納されます。 整数キーは、レジストリを値のストアとして使用するときに使用されます。 キーは、 registry.store



を呼び出すことによって作成され、その後registry.store



への読み取りと書き込みに使用され、値が消去され、キーが解放されると、 nil



書き込まれます。

 auto k = ctx.registry.store(ctx.upvalues[1]); // decltype(k) == int ctx.registry [k] = nil; //  k       store
      
      





同等のコード
 lua_pushvalue(ctx, lua_upvalueindex(1)); auto k = luaL_ref(ctx, LUA_REGISTRYINDEX); luaL_unref(ctx, LUA_REGISTRYINDEX, k);
      
      





機能
先ほど、Luaを使用してクロージャを作成できると述べました。 Context



オブジェクトはこれにclosure



関数を使用します。 closure



関数は、 CFunction



とクロージャーに格納される値を受け取ります。 結果は一時オブジェクト、つまり完全なLua値です。

CFunction



代わりに、 CFunction



をすぐに指定できますが、この明るさには独自の価格があります。 最初の上位値は、結果のクロージャーで予約されます(ラッパーはどのLFunctionでも同じであるため、関数のアドレスはそこに格納されます)。 同じ関数がLFunction



透過的な移行に使用され、同じ結果になります。 これは、何も予約しないmkcf



テンプレートとの違いですが、関数ごとに個別のラッパー関数を作成します。



チャンクを作成することもできます:コンパイルされたLuaコード。 テキスト自体はchunk



メソッドを使用してコンパイルされ、ファイルのコンテンツはload



を使用してコンパイルされload



。 「完了して忘れられた」場合、 runString



とまったく同じrunString



runFile



があります。 使用に関しては、チャンクは一般的な機能です。



wrap



メソッドを使用して、互換性のない関数からクロージャーを作成することもできます。 Luaスタックから引数を取り、関数で受け入れられる値に変換し、呼び出しを行い、結果を戻り値としてLuaスタックに配置するラッパーを自動的に作成します。 デフォルトでは、これはユーザーデータを含むすべてのサポートされているタイプで機能します。 これで十分でない場合(たとえば、 vector



格納された行で何かをする必要がある場合、 特別なマクロを使用して、ある方向または別の方向への変換を指定することもできます。

関数を暗黙的に移行するときに機能するのはwrap



兄弟のvwrap



メソッドはほとんどすべて同じことを行い、ラップされる関数によって返される値のみを無視します。







価値の移行



Lua API ++は、次のネイティブタイプをサポートしています。

数値
int



unsigned int



long long



unsigned long long



float



double



ひも
const char*



std::string



機能
CFunction: int (*) (lua_State*)



LFunction: Retval (*) (Context&)



任意の関数
メンバー関数
雑多
Nil



bool



LightUserData: void*



登録ユーザータイプ


表にリストされている型の値はLuaスタックに移行でき、その逆も可能です(もちろん、 Nil



と「ラッパー」関数は例外で、ラッパーへのポインターのままです)。


値型に組み込まれた暗黙的な変換演算子とcast



テンプレート関数を使用して、逆移行が実行されます。Lua値に、目的のデータに変換できないデータが含まれている場合、例外がスローされます。
optcast



関数は、例外ではなく「フォールバック」値を返します。


 int a = val; auto b = val.cast<int>(); auto c = val.optcast<int>(42);
      
      





同等のコード
 if(!lua_isnumber(ctx, valIdx)){ lua_pushstring(ctx, "Conversion error"); return lua_error(ctx); } int a = lua_tointeger(ctx, valIdx); if(!lua_isnumber(ctx, valIdx)){ lua_pushstring(ctx, "Conversion error"); return lua_error(ctx); } auto b = lua_tointeger(ctx, valIdx); auto c = lua_isnumber(ctx, valIdx) ? lua_tointeger(ctx, valIdx) : 42;
      
      







is



関数を使用して目的の型との互換性を確認し、typeを使用して、保存されている値の型を直接確認できます。


 if(val.is<double>()) ...; if(val.type() == ValueTypes::Number) ...;
      
      










単一値操作



割り当て
Valueがある場合、一般的なケースでは、他のValueとネイティブの両方に何かを割り当てることができます。 ただし、これは一時的な値、たとえば関数呼び出しの結果や長さには適用されません。 =



記号の左側にあると、トリッキーなエラーが発生します。
ただし、インデックス付けやメタテーブルなど、その他の一時的な値の割り当ては非常に受け入れられます。 実行されたアクションの意味に従って、割り当てられるものとできないものを推測するのは簡単です。



メタテーブル
mt



メソッドは、読み取りと書き込みが可能な値のメタテーブルへのアクセスを提供します。


 { Table mt = val.mt(); val.mt() = nil; }
      
      





同等のコード
 if(!lua_getmetatable(ctx, valIdx)){ lua_pushstring(ctx, "The value has no metatable"); return lua_error(ctx); } int mtIdx = lua_gettop(ctx); lua_pushnil(ctx); lua_setmetatable(ctx, valIdx); lua_pop(ctx, 1);
      
      







長さ
len



関数の操作は、Luaのバージョンによって異なります。5.1互換モードではネイティブのsize_t



返し、5.2モードでは一時的な値を返します。




索引付け
テーブルのキー要素はインデックスによってアクセスされ、キーはサポートされている任意のタイプにすることができます。 ただし、関数をラップすると、新しいクロージャが作成されることを覚えておく必要があります。

 void myFunc(); Retval example(Context& c) { Table t(c); t[myFunc] = 42; //  myFunc   ... assert(t[myFunc].is<Nil>()); //   -  ,   . t[mkcf<example>] = 42.42; //     CFunction,   "" assert(t[mkcf<example>] == 42.42); }
      
      





同等のコード
 void myFunc(); int wrapped_void_void(lua_State* s) { if(!lua_islightuserdata(ctx, lua_upvalueindex(1))) { lua_pushstring(ctx, "Conversion error"); return lua_error(ctx); } void (*fptr) () = reinterpret_cast<void(*)()>(lua_touserdata(ctx, lua_upvalueindex(1))); fptr(); return 0; } int mkcf_myFunc(lua_State* s) { myFunc(); return 0; } int example(lua_State* ctx) { lua_createtable(ctx, 0, 0); int t = lua_gettop(ctx); lua_pushlightuserdata(ctx, reinterpret_cast<void*>(&myFunc)); lua_pushcclosure(ctx, &wrapped_void_void, 1); lua_pushinteger(ctx, 42); lua_settable(ctx, t); lua_pushlightuserdata(ctx, reinterpret_cast<void*>(&myFunc)); lua_pushcclosure(ctx, &wrapped_void_void, 1); lua_gettable(ctx, t); assert(lua_isnil(ctx, -1)); lua_pushcfunction(ctx, &mkcf_myFunc); lua_pushnumber(ctx, 42.42); lua_settable(ctx, t); lua_pushcfunction(ctx, &mkcf_myFunc); lua_gettable(ctx, t); lua_pushnumber(ctx, 42.42); int result = lua_compare(ctx, -1, -2, LUA_OPEQ); lua_pop(ctx, 2); assert(result == 1); }
      
      







関数呼び出し
Lua関数は、通常の括弧を使用して呼び出されます。 もっと表現力豊かなものが必要な場合のために、明示的な呼び出しのためのcall



メソッドがあります。
pcall



呼び出しはエラーを防ぎます。


 int x = fn(1); int y = fn.call(1); //    int z = fn.pcall(1); //     ,   
      
      





複数の戻り値
呼び出しの結果を通常の値として安全に使用できることがわかりました。 しかし、一度に複数の値を返す関数はどうでしょうか? たとえば、Luaでこのような関数を使用します。
 function mrv() return 2, 3, 4; end
      
      





一度にいくつかのオプションがあります。

まず、呼び出し式を使用せずに呼び出しの結果を完全に無視できます。 返されるすべての値は単純にスローされます。

 mrv();
      
      





同等のコード
 lua_pushvalue(ctx, mrvIdx); lua_call(ctx, 0, 0);
      
      





次に、呼び出し式を通常のLua値として使用できます。 次に、最初に返された値のみが使用され(関数が何も返さない場合はnil



)、残りは再びスローされます。


 Value x = mrv(); // x == 2
      
      





同等のコード
 lua_pushvalue(ctx, mrvIdx); lua_call(ctx, 0, 1); int x = lua_gettop(ctx);
      
      





第三に、値のシーケンス(たとえば、関数パラメーター)を含むコンテキストでは、呼び出し式が展開されます:返された値はシーケンスの一部になります。

 print(1, mrv(), 5); //  1 2 3 4 5
      
      





同等のコード
 lua_pushvalue(ctx, printIdx); int oldtop = lua_gettop(ctx); lua_pushinteger(ctx, 1); lua_pushvalue(ctx, mrvIdx); lua_call(ctx, 0, LUA_MULTRET); lua_pushinteger(ctx, 5); int nArgs = lua_gettop(ctx) - oldtop; lua_call(ctx, nArgs, 0);
      
      





第4に、このためにValset



に考案されたすべてをValset



に保存することができます。


 Valset vs = mrv.pcall(); // vs.size() == 3, vs.success() == true
      
      





同等のコード
 int vsBegin = lua_gettop(ctx) + 1; lua_pushvalue(ctx, mrvIdx); bool vsSuccess = lua_pcall(ctx, 0, LUA_MULTRET) == LUA_OK; int vsSize = lua_gettop(ctx) + 1 - vsBegin;
      
      





Valset



は、保護された呼び出しが成功したかどうかも記憶します(この情報を取得する唯一の方法です)。
失敗した場合は、エラーメッセージが含まれます。 嬉しいことに、 Valset



は呼び出し式と同じ方法で値のリストに展開できます。


 print(1, vs, 5); //  1 2 3 4 5
      
      





同等のコード
 lua_pushvalue(ctx, printIdx); int oldTop = lua_gettop(ctx); lua_pushInteger(ctx, 1); lua_pushvalue(ctx, mrvIdx); for(auto i = 0; i < vsSize; ++i) lua_pushvalue(ctx, vsBegin + i); lua_pushinteger(ctx, 5); int nArgs = lua_gettop(ctx) - oldtop; lua_call(ctx, nArgs, 0);
      
      





Valset



. STL- STL, «» Valref



. Valset



, push_back



pop_back



. Valref



, ( Valset



), . , .








, Value- , :

 string s = "The answer to question " & val & " is " & 42;
      
      





 lua_pushstring(ctx, "The answer to question "); lua_pushvalue(ctx, valIdx); lua_pushstring(ctx, " is "); lua_pushinteger(ctx, 42); lua_concat(ctx, 4); string s = lua_tostring(ctx, -1); lua_pop(ctx, 1);
      
      





& . , «» . , Valset



.



Lua, .



5.2 , , «» ^



.








, Table



, Valref



. , , , . raw



, , , iterate



, for_each



. , iterate



( , , ), -. Valref



true



, false



, . :

 Table t = ctx.global["myTable"]; t.iterate([] (Valref k, Valref v) { cout << int(k) << int(v); });
      
      





 lua_getglobal(ctx, "myTable"); if(!lua_istable(ctx, -1)){ lua_pushstring(ctx, "Conversion error"); return lua_error(ctx); } int t = lua_gettop(ctx); lua_pushnil(ctx); size_t visited = 0; while(lua_next(ctx, t)) { ++ visited; if(!lua_isnumber(ctx, -2) || !lua_isnumber(ctx, -1)){ lua_pushstring(ctx, "Conversion error"); return lua_error(ctx); } cout << lua_tointeger(ctx, -2) << lua_tointeger(ctx, -1); lua_pop(ctx, 1); }
      
      





iterate



.



Table



array



records



. .

 fn(Table::array(ctx, "one", 42, Table::array(ctx, 1, 2, 3))); //  ? !
      
      





 lua_pushvalue(ctx, fn); lua_createtable(ctx, 3, 0); lua_pushinteger(ctx, 1); lua_pushstring(ctx, "one"); lua_settable(ctx, -3); lua_pushinteger(ctx, 2); lua_pushinteger(ctx, 42); lua_settable(ctx, -3); lua_pushinteger(ctx, 3); lua_createtable(ctx, 3, 0); lua_pushinteger(ctx, 1); lua_pushinteger(ctx, 1); lua_settable(ctx, -3); lua_pushinteger(ctx, 2); lua_pushinteger(ctx, 2); lua_settable(ctx, -3); lua_pushinteger(ctx, 3); lua_pushinteger(ctx, 3); lua_settable(ctx, -3); lua_settable(ctx, -3); lua_call(ctx, 1, 0);
      
      





, . , array



, 1. , Valset



.



records



, -. .

 x.mt() = Table::records(ctx, "__index", xRead, "__newindex", xWrite, "__gc", xDestroy );
      
      





 lua_createtable(ctx, 0, 3); lua_pushstring(ctx, "__index"); lua_pushlightuserdata(ctx, reinterpret_cast<void*>(&xRead)); lua_pushcclosure(ctx, &wrapped_signature_1, 1); lua_settable(ctx, -3); lua_pushstring(ctx, "__newindex"); lua_pushlightuserdata(ctx, reinterpret_cast<void*>(&xWrite)); lua_pushcclosure(ctx, &wrapped_signature_2, 1); lua_settable(ctx, -3); lua_pushstring(ctx, "__gc"); lua_pushlightuserdata(ctx, reinterpret_cast<void*>(&xDestroy)); lua_pushcclosure(ctx, &wrapped_signature_3, 1); lua_settable(ctx, -3); lua_setmetatable(ctx, x);
      
      










. - , : , cast



, .

. LUAPP_USERDATA



. , , . , registry



-, :

 LUAPP_USERDATA(MyType, "MyType Lua ID") Retval setup(Context& ctx) { ctx.mt<MyType>() = Table::records(ctx); //   ,     ctx.registry["MyType Lua ID"] }
      
      





Lua . , , «» , — .



Lua , Lua . Lua API++ placement new , , . POD-. , , .



. , -, , . , Lua , :

 #include <vector> using dvec = std::vector<double>; //      LUAPP_USERDATA(dvec, "Number array") //      dvec aCreate(size_t size) //    . { //  -       . return dvec(size); //  RVO        } void aDestroy(dvec& self) //  -        . { self.~dvec(); } void aWrite(dvec& self, size_t index, double val) //          __newindex { self.at(index) = val; //     at,     Lua } Retval setup(Context& c) { //   c.mt<dvec>() = Table::records(c, //     "__index", static_cast<double& (dvec::*)(size_t)> (&dvec::at), //         at //    (const  -const),      "__newindex", aWrite, "__len", dvec::size, //   size  vector ,    "__gc", aDestroy ); c.global["createArray"] = aCreate; //      return c.ret(); }
      
      










おわりに



, Lua . Lua API ++, . Lua Lua API (coroutine, string buffers, ).



, Lua API, Lua. inline



, Lua API , , Link time code generation (LTO GCC). header-only. inline



Lua.



, C++11 , Lua STL. Boost Unit Test Framework .



Lua 5.2 ( 5.3 ), 5.1, LuaJIT.



Lua API++ MIT — , Lua, . HTML, .



, - .



All Articles