High performance Lua interpreter implemented in C# for .NET and Unity
English | 日本語
Lua-CSharpはC#実装のLuaインタプリタを提供するライブラリです。Lua-CSharpを導入することで、.NETアプリケーション内で簡単にLuaスクリプトを組み込むことが可能になります。
// TODO: ベンチマークの追加
Lua-CSharpを利用するには.NET Standard2.1以上が必要です。パッケージはNuGetから入手できます。
dotnet add package LuaCSharp
Install-Package LuaCSharp
Lua-CSharpをUnityで利用することも可能です。(Mono/IL2CPPの両方で動作します)
NuGet > Manage NuGet PackagesからNuGetウィンドウを開き、LuaCSharpパッケージを検索してインストールします。LuaStateクラスを利用することでC#上からLuaスクリプトを実行することが可能です。以下はLuaで記述された簡単な演算を評価するサンプルコードです。
using Lua;
// LuaStateを作成する
var state = LuaState.Create();
// DoStringAsyncで文字列のLuaスクリプトを実行する
var results = await state.DoStringAsync("return 1 + 1");
// 2
Console.WriteLine(results[0]);
[!WARNING]
LuaStateはスレッドセーフではありません。同時に複数のスレッドからアクセスしないでください。
Luaスクリプト上の値はLuaValue型で表現されます。LuaValueの値はTryRead<T>(out T value)またはRead<T>()で読み取ることが可能です。
var results = await state.DoStringAsync("return 1 + 1");
// double
var value = results[0].Read<double>();
また、Typeプロパティから値の型を取得できます。
var isNil = results[0].Type == LuaValueType.Nil;
Lua-C#間の型の対応を以下に示します。
| Lua | C# |
|---|---|
nil |
LuaValue.Nil |
boolean |
bool |
string |
string |
number |
double,float |
table |
LuaTable |
function |
LuaFunction |
userdata |
LuaUserData |
thread |
LuaThread |
C#側からLuaValueを作成する際には、変換可能な型の場合であれば暗黙的にLuaValueに変換されます。
LuaValue value;
value = 1.2; // double -> LuaValue
value = "foo"; // string -> LuaValue
value = new LuaTable() // LuaTable -> LuaValue
LuaのテーブルはLuaTable型で表現されます。これは通常のLuaValue[]やDictionary<LuaValue, LuaValue>のように使用できます。
// Lua側でテーブルを作成
var results = await state.DoStringAsync("return { a = 1, b = 2, c = 3 }")
var table1 = results[0].Read<LuaTable>();
// 1
Console.WriteLine(table1["a"]);
// テーブルを作成
results = await state.DoStringAsync("return { 1, 2, 3 }")
var table2 = results[0].Read<LuaTable>();
// 1 (Luaの配列は1-originであることに注意)
Console.WriteLine(table2[1]);
state.EnvironmentからLuaのグローバル環境にアクセスできます。このテーブルを介して簡単にLua-C#間で値をやり取りすることが可能です。
// a = 10を設定
state.Environment["a"] = 10;
var results = await state.DoStringAsync("return a");
// 10
Console.WriteLine(results[0]);
Luaの標準ライブラリを利用することも可能です。state.OpenStandardLibraries()を呼び出すことで、LuaStateに標準ライブラリのテーブルを追加します。
using Lua;
using Lua.Standard;
var state = LuaState.Create();
// 標準ライブラリを追加
state.OpenStandardLibraries();
var results = await state.DoStringAsync("return math.pi");
Console.WriteLine(results[0]); // 3.141592653589793
標準ライブラリについてはLua公式のマニュアルを参照してください。
[!WARNING] Lua-CSharpは標準ライブラリの全ての関数をサポートしているわけではありません。詳細は互換性の項目を参照してください。
Luaの関数はLuaFunction型で表現されます。LuaFunctionによってLuaの関数をC#側から呼び出したり、C#で定義した関数をLua側から呼び出したりすることが可能です。
-- lua2cs.lua
local function add(a, b)
return a + b
end
return add;
var results = await state.DoFileAsync("lua2cs.lua");
var func = results[0].Read<LuaFunction>();
// 引数を与えて関数を実行する
var funcResults = await func.InvokeAsync(state, [1, 2]);
// 3
Console.WriteLine(funcResults[0]);
LuaFunction.Create()を用いてラムダ式からLuaFunctionを作成できます。
// グローバル環境に関数を追加
state.Environment["add"] = LuaFunction.Create((args, ct) =>
{
return [args[0].Read<double>() + ]
});
// Luaスクリプトを実行
var results = await state.DoFileAsync("cs2lua.lua");
// 3
Console.WriteLine(results[i]);
-- cs2lua.lua
return add(1, 2)
ただし、LuaFunction.Create()で作成された関数は呼び出し時に余計なアロケーションを発生させます。最良のパフォーマンスを得るためにはLuaFunctionを継承したクラスを実装してください。
LuaのコルーチンはLuaThread型で表現されます。
コルーチンはLuaスクリプト内で利用できるだけでなく、Luaで作成したコルーチンをC#で待機することも可能です。
-- coroutine.lua
local co = coroutine.create(function()
for i = 1, 10 do
print(i)
coroutine.yield()
end
end)
return co
var results = await state.DoFileAsync("coroutine.lua");
var co = results[0].Read<LuaThread>();
for (int i = 0; i < 10; i++)
{
var resumeResults = await co.ResumeAsync(state);
// coroutine.resume()と同様、成功時は最初の要素にtrue、それ以降に関数の戻り値を返す
// 1, 2, 3, 4, ...
Console.WriteLine(resumeResults[1]);
}
Luaではrequire関数を用いてモジュールを読み込むことができます。通常のLuaではpackage.searchersの検索関数を用いてモジュールの管理を行いますが、Lua-CSharpでは代わりにILuaModuleLoaderがモジュール読み込みの機構として提供されています。
public interface ILuaModuleLoader
{
bool Exists(string moduleName);
ValueTask<LuaModule> LoadAsync(string moduleName, CancellationToken cancellationToken = default);
}
これをLuaState.ModuleLoaderに設定することでモジュールの読み込み方法を変更することができます。デフォルトのLoaderにはluaファイルからモジュールをロードするFileModuleLoaderが設定されています。
また、CompositeModuleLoader.Create(loader1, loader2, ...)を利用することで複数のLoaderを組み合わせたLoaderを作成できます。
state.ModuleLoader = CompositeModuleLoader.Create(
new FileModuleLoader(),
new CustomModuleLoader()
);
また、ロード済みのモジュールは通常のLua同様にpackage.loadedテーブルにキャッシュされます。これはLuaState.LoadedModulesからアクセスすることが可能です。
Luaスクリプトの解析エラーや実行時例外はLuaExceptionを継承した例外をスローします。これをcatchすることでエラー時の処理を行うことができます。
try
{
await state.DoFileAsync("filename.lua");
}
catch (LuaParseException)
{
// 構文にエラーがあった際の処理
}
catch (LuaRuntimeException)
{
// 実行時例外が発生した際の処理
}
Lua-CSharpは.NETとの統合を念頭に設計されているため、C実装とは互換性がない仕様がいくつか存在します。
Lua-CSharpはLuaバイトコードをサポートしません(luacなどは使用できません)。実行可能なのはLuaソースコードのみです。
Lua-CSharpで利用される文字コードはUTF-16です。通常Luaは1バイトで1文字を表すエンコーディングを前提としているため、文字列まわりの動作が大きく異なります。
例えば、以下のコードの出力結果は通常のLuaでは15ですが、Lua-CSharpでは5です。
local l = string.len("あいうえお")
print(l)
Stringライブラリの関数はすべて文字列をUTF-16として扱う実装に変更されていることに注意してください。
Lua-CSharpはC#で実装されているため.NETのGCに依存しています。そのため、メモリ管理に関する動作が通常のLuaとは異なります。
collectgarbage()は利用可能ですが、これは単にGC.Collect()の呼び出しです。引数の値は無視されます。また、弱参照テーブル(week tables)はサポートされません。
moduleライブラリはrequire()およびpackage.loadedのみが利用でき、それ以外の関数は実装されていません。これはLua-CSharpは.NETに最適化された独自のモジュール読み込みの機能を有するためです。
詳細はモジュールの読み込みの項目を参照してください。
現在debugライブラリの実装は提供されていません。これはLua-CSharpの内部実装がC実装とは大きく異なり、同じAPIのライブラリを提供することが難しいためです。これについては、v1までに実装可能な一部のAPIのみの提供、または代替となるデバッグ機能を検討しています。
このライブラリはMITライセンスの下で提供されています。