C++ に Lua を良い感じに組み込むためのライブラリ Sol v2.x の使い方まとめ

## Sol v2.x

Sol は、C++ 用の Lua バインダです。ヘッダのインクルードのみで利用でき、Boost に依存しない、お手軽に使えるライブラリで個人的におすすめです。Lua バージョン 5.1 以上に対応しており、LuaJIT もいけるみたいです（未確認）。現在活発に開発が行われており、週一ペースくらいで新バージョンがリリースされているようです（→ https://github.com/ThePhD/sol2/releases ）。

以下公式のサイトです。

* https://github.com/ThePhD/sol2
* http://sol2.readthedocs.io/en/latest/index.html
    * 公式ドキュメント。["tutorial: quick ‘n’ dirty"](http://sol2.readthedocs.io/en/latest/tutorial/all-the-things.html) を眺めると大体の雰囲気が分かります


ということで、私がよく使う機能を中心に簡単にまとめようと思います。後々便利な機能が見つかったら、都度書き加えていく予定です。参考までに私はこんな環境で使っています。

* Windows 10 64bit
* Visual Studio Community 2015 Update 3
* Lua 5.2.4


## 導入

https://github.com/ThePhD/sol2/releases から `sol.hpp` をダウンロードします。

もしくはこちら。
https://github.com/ThePhD/sol2/tree/develop/single/sol

※リポジトリのルートにある同名のファイルとは内容が違うので注意。


### 参考：Windows用 Lua バイナリの導入

Lua をビルドするのは面倒だったので [Lua Binaries](https://sourceforge.net/projects/luabinaries/files/5.2.4/Windows%20Libraries/Dynamic/) にあるバイナリを使っています。
https://sourceforge.net/projects/luabinaries/files/5.2.4/Windows%20Libraries/Dynamic/
この中の [lua-5.2.4_Win64_dll14_lib.zip](https://sourceforge.net/projects/luabinaries/files/5.2.4/Windows%20Libraries/Dynamic/lua-5.2.4_Win64_dll14_lib.zip/download) をダウンロードしました。


## まずは最小限のプログラム

Hello World してみます。

```cpp
#include <sol.hpp>

int main()
{
	sol::state lua;
	lua.open_libraries(sol::lib::base, sol::lib::package);
	lua.script("print('Hello World')");
}
```

"Hello World" と表示されたでしょうか？ コンパイルできない場合は、`sol.hpp` を配置したフォルダをインクルードパスに含めてください。

リンクに失敗する場合は、Lua のライブラリをリンクしてください。Lua 5.2 なら `lua52.lib` です。また、実行時に `lua52.dll` が必要です。これらについては、Lua 自体の導入に関する他の記事が参考になると思います。


## いろいろ試してみる

### Lua スクリプトの実行

```cpp
sol::state lua; //以降省略します

...

// Lua スクリプトを文字列で渡して実行する
lua.script("print(123)");  // => '123'

// 外部の Lua スクリプトを読み込み実行する
lua.script_file("something.lua");
```

### 変数

#### Luaの変数に値を設定してみる

```cpp
lua["intval"] = 100;
lua["pi"] = 3.14;
lua["some_text"] = "abcdef";

lua.script_file("var.lua")
```

```lua:var.lua
print(intval)   -- => '100'
print(pi)  -- => '3.14'
print(some_text)  -- => 'abcdef'
```

#### Luaの変数の値を取得してみる

```cpp
lua.script_file("var2.lua")

std::cout << lua["a"].get<int>(); // => '100'
std::cout << lua["b"].get<int>(); // => '200'
std::cout << lua["not_exist"].get_or(999);  // => '999'
```

```lua:var2.lua
a = 100
b = 200
c = 300
```


### 関数

```cpp
// C++側で定義した関数をLuaから呼ぶ

lua["func1"] = &func;  // どこかに定義された関数
lua["func2"] = [](const int x) { return x * 2; };  // ラムダでもいい

lua.script("print(func2(10))");  // => '20'
```

```cpp
// Lua側で定義した関数をC++から呼ぶ

lua.script("function f(x) return x * 3 end");

int ret = lua["f"](100);  // => 300
```

### テーブル

```cpp
lua.script("t = { 10, 20, 30 }");
lua.script("t2 = { a = 'text', b = 123, c = 3.14 }");

std::cout << lua["t"][3].get<int>();  // => '30'
std::cout << lua["t2"]["a"].get<std::string>();  // => 'text'
```

```cpp
lua["t3"] = lua.create_table_with("x", 100, "y", 200);  // t3 = { x = 100, y = 200 }

std::cout << lua["t3"]["y"].get<int>();  // => '200'
```

入れ子になっているテーブルも `lua["a"]["b"]["c"]["d"]` という感じでアクセスできます。

```cpp
// C++の関数へLuaのテーブルを渡す

lua["f"] = [](sol::table& t) -> int { return t[1].get<int>(); };

lua.script("print(f({10, 20, 30}))");  // => '10'
```

### クラス

下記のようにして C++ のクラスを Lua 側でも使用することができます。
詳細については公式ドキュメントの ["C++ in Lua"](http://sol2.readthedocs.io/en/latest/tutorial/cxx-in-lua.html) をご覧ください。

#### 例1：C++クラスのインスタンスをLua側で生成し使用する

```cpp:main.cpp
#include <sol.hpp>

int main()
{
	sol::state lua;
	lua.open_libraries(sol::lib::base, sol::lib::package);

	// クラスAをC++とLua間でやりとりしたい
	class A
	{
	public:
		A(const int val = 0) : val_(val) {}

		int f() {
			return val_ * 100;
		}

		int getVal() {
			return val_;
		}

		void setVal(const int val) {
			val_ = val;
		}

		int x = 0;

	private:
		int val_;
	};

	// ユーザー定義型の登録
	lua.new_usertype<A>(
		// 型名
		"A",

		//コンストラクタ
		sol::constructors<sol::types<>, sol::types<const int>>(),

		// メンバ関数
		"f", &A::f,

		// メンバ変数
		"x", &A::x,

		// プロパティ
		"val", sol::property(&A::getVal, &A::setVal),

		// ラムダ関数も使用可
		"y", [](A& a, const int n) ->int { return a.x * n; }
		);

	lua.script_file("a.lua");
}
```

```lua:a.lua
a = A.new(100)
print(a:f())  -- => '10000'

a.val = 456
print(a.val)  -- => '456'
print(a:f())  -- => '45600'

a.x = 123
print(a.x)  -- => '123'

print(a:y(200))  -- => '24600'
```

#### 例2：関数に渡してみる

```cpp
#include <sol.hpp>
#include <memory>

struct B
{
	void hello() { std::cout << "hello\n"; }
};

void f1(B* b)
{
	std::cout << "f1\n";
	b->hello();
}

int main()
{
	sol::state lua;
	lua.open_libraries(sol::lib::base, sol::lib::package);

	lua.new_usertype<B>(
		"B",
		"hello", &B::hello);

	lua["f1"] = &f1;

	// C++関数f1にLua側で生成したインスタンスを渡す
	lua.script_file("b.lua");

	// Lua側の関数f2にC++側で生成したインスタンスを渡す
	B b;
	lua["f2"](b);  //値渡し
	lua["f2"](&b);  //ポインタ渡し
	lua["f2"](std::ref(b));  //参照渡し

	// shared_ptrも渡せる
	std::shared_ptr<B> pb(new B());
	lua["f2"](pb);
}
```

```lua:b.lua
obj = B.new()
f1(obj)

function f2(b)
    print("f2")
    b:hello()
end
```

### コルーチン

```lua:coro.lua
function f(arg)
	coroutine.yield(arg + 1)
	coroutine.yield(arg + 2)
	coroutine.yield(arg + 4)
	return 0
end
```

```cpp:coro.cpp
#include <sol.hpp>

int main()
{
	sol::state lua;
	lua.open_libraries(sol::lib::base, sol::lib::coroutine);

	lua.script_file("coro.lua");

	sol::thread th = sol::thread::create(lua.lua_state());
	sol::coroutine coro = th.state()["f"];

	std::cout << coro(1).get<int>() << "\n";  // => '2'
	std::cout << coro().get<int>() << "\n";   // => '3'
	std::cout << coro().get<int>() << "\n";   // => '5'
	std::cout << coro().get<int>() << "\n";   // => '0'

	// あるいは
	//while (coro.runnable())
	//{
	//	// ※コルーチンの引数は、初回に渡したものはコルーチン関数の引数となり、以降渡したものはyieldの戻り値となる
	//	std::cout << coro(1).get<int>() << "\n";
	//}
}
```

## 環境

sol 2.17 から、参照する環境テーブルを指定できるようになりました。
[examples/environments.cpp](https://github.com/ThePhD/sol2/blob/develop/examples/environments.cpp)

```cpp
	sol::state lua;

	lua.script("a = 123");

	// 新しい環境テーブルを作成
	sol::environment env1(lua, sol::create);

	lua.script("b = 456", env1);

	std::cout << lua["a"].get_or(-1) << "\n";   // => '123'
	std::cout << env1["a"].get_or(-1) << "\n";  // => '-1'

	std::cout << lua["b"].get_or(-1) << "\n";   // => '-1'
	std::cout << env1["b"].get_or(-1) << "\n";  // => '456'
```



## まとめてみて雑感など

* ヘッダ 1 個で使えるのがお手軽でとても良い
* C++11/14 で書かれているのも使いやすいポイント（ラムダ関数でバインドできたり）
* Lua の環境とテーブルに関するインターフェースが統一されていて使いやすい
* wstring をうまく扱う方法が分からない（Sol がスタックへの push/pop 時に string/wstring の変換をしてくれているようではある）