C++ & Lua : C++ のネイティブデータ型を Lua へ提供し、操作する例

# 状況

C++ に Lua を組み込み実行時に Lua スクリプトを C++ コードから呼び出して処理するプログラムにおいて、 C++ のネイティブデータ型を Lua へ提供し、これを操作したい。

## 具体的な例

例として次に挙げるような C++ のユーザー定義型を要素とする `std::vector` コンテナー型を Lua へ提供し、操作させたいとする。

```cpp
struct f32vec4
{
  float x, y, z, w;
  f32vec4( const float a = 0, const float b = 0, const float c = 0, const float d = 0 )
    : x( a ), y( b ), z( c ), w( d )
  { }
};
using native_data_type = std::vector< f32vec4 >;
```

Lua コードからは次のように制御したいとする

```lua
local obj = mylib.make_obj()
local nan = 0 / 0
obj:push( 1.23, 3.45, 6.78, nan  )
obj:push( 12.3, 34.5, 67.8, 90.1 )
```

実際にはmake_objで引数を与えて `std::vector` を一定の要素数で生成したり `reserve` したり、あるいは C++ コード側から呼び出す Lua コードの関数の return として `native_data_type` を受け取るなど様々なニーズが考えられるが、この記事では C++ のネイティブデータ型 `native_data_type` を Lua へ提供し、 Lua で `push` により `emplace_back` し、 Lua がオブジェクトを破棄する時点で `native_data_type` が格納している値を C++ コードで出力するに留める。

# コード例

main.cxx

```cpp
#include <lua.hpp>
#include <iostream>
#include <vector>
#include <new>

struct f32vec4
{
  float x, y, z, w;
  f32vec4( const float a = 0, const float b = 0, const float c = 0, const float d = 0 )
    : x( a ), y( b ), z( c ), w( d )
  { }
};
using native_data_type = std::vector< f32vec4 >;

auto luaopen_mylib( lua_State* L )
{
  static const luaL_Reg obj_definitions[] =
  { { "method_test"
    , []( auto L )
      {
        std::cout << "obj.method_test : " << luaL_checkudata(L, 1, "obj_type") << "\n";
        return 0;
      }
    }
  , { "push"
    , []( auto L )
      {
        reinterpret_cast<native_data_type*>(luaL_checkudata(L, 1, "obj_type"))->emplace_back
        ( luaL_checknumber(L, -4)
        , luaL_checknumber(L, -3)
        , luaL_checknumber(L, -2)
        , luaL_checknumber(L, -1)
        );
        std::cout << "obj.push" << "\n";
        return 0;
      }
    }
  , { nullptr, nullptr }
  };
	
  static const luaL_Reg mylib_definitions[] =
  { { "make_obj"
    , []( auto L )
      {
        std::cout << "mylib.make_obj\n";
        auto p = lua_newuserdata( L, sizeof( native_data_type ) );
        new( p ) native_data_type(); // placement new
        luaL_setmetatable( L, "obj_type" );
        return 1;
      }
    }
  ,	{ nullptr, nullptr }
  };
	
  luaL_newlib( L, mylib_definitions ); // stack: mylib
  
  luaL_newmetatable(L, "obj_type" ); // stack: mylib meta
  luaL_newlib(L, obj_definitions );
  lua_setfield(L, -2, "__index" ); // stack: mylib meta
  
  lua_pushstring( L, "__gc" );
  lua_pushcfunction
  ( L
  , []( auto L )
    {
      const auto p = luaL_checkudata( L, 1, "obj_type" );
      std::cout << "obj.__gc : " << p << "\n";
      std::cout << "=== print data ===\n";
      for ( const auto& e : *reinterpret_cast< native_data_type* >( p ) )
        std::cout << "  " << e.x << " " << e.y << " " << e.z << " " << e.w << "\n";
      std::cout << "=== === === ===\n";
      return 0;
    }
  ); // stack: mylib meta "__gc" fptr
  lua_settable(L, -3); // stack: mylib meta
  
  lua_pop(L, 1); // stack: mylib
  
  return 1;
}

auto main() -> int
{
  lua_State *L = luaL_newstate();
  luaL_openlibs(L);
  
  luaL_requiref( L, "mylib", luaopen_mylib, 1 );
  lua_pop( L, 1 ); // requiref leaves the library table on the stack
  
  luaL_dofile( L, "a.lua" );
  
  lua_close( L );
}
```

a.lua

```lua
local obj = mylib.make_obj()

obj:method_test()

local nan = 0 / 0

obj:push( 1.23, 3.45, 6.78, nan  )
obj:push( 12.3, 34.5, 67.8, 90.1 )
```

## 実行結果

```
mylib.make_obj
obj.method_test : 0xbcc358
obj.push
obj.push
obj.__gc : 0xbcc358
=== print data ===
  1.23 3.45 6.78 -nan
  12.3 34.5 67.8 90.1
=== === === ===
```

## Wandbox

- http://melpon.org/wandbox/permlink/kZcqoQVDyKAf4k3j
    - `lua.hpp` のインクルードと `liblua.a` のリンクがやや特殊になっている点についてはこちらを参照 -> https://github.com/melpon/wandbox/issues/173

# 要点の概説

- C++ から Lua へ `mylib` と `obj_type` の2つの定義を `luaopen_mylib` を `luaL_requiref(L, "mylib", luaopen_mylib, 1);` で与えている
- `mylib` には `obj_type` を生成する `make_obj` メソッドを `mylib_definitions` で定義している
    - `lua_newuserdata` は C の `malloc` 挙動のようなものでメモリー領域の確保しか行わないので、 `new( p ) native_data_type()` として placement new により確保済みのメモリー領域に `native_data_type` を構築している
- `obj_type` には C++ レベルでの `emplace_back` を行う `push` メソッドとより簡単なテスト用のメソッド `method_test` を定義している
- `obj_type` には追加で `lua_pushstring` `lua_pushfunction` 等で `__gc` メソッドも定義し、 Lua の GC がオブジェクトを破棄する際に C++ コードレベルで `native_data_type` として保持する要素を出力している
    - 確保してあるメモリー領域の開放処理は Lua ライブラリーが行い、また `native_data_type` の構築も placement new で行っているため `delete` の必要は無い

# References

- [lua-users - wiki - Library With Userdata Example](http://lua-users.org/wiki/LibraryWithUserdataExample)
    - 今回の記事のソースコードは比較的モダンな C++ 機能も用いているが、原則的にはこの記事で紹介されているソースコード例を元にしています。