NuLua 0.0.3

There is a newer version of this package available.
See the version list below for details.
dotnet add package NuLua --version 0.0.3
                    
NuGet\Install-Package NuLua -Version 0.0.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NuLua" Version="0.0.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NuLua" Version="0.0.3" />
                    
Directory.Packages.props
<PackageReference Include="NuLua" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add NuLua --version 0.0.3
                    
#r "nuget: NuLua, 0.0.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package NuLua@0.0.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=NuLua&version=0.0.3
                    
Install as a Cake Addin
#tool nuget:?package=NuLua&version=0.0.3
                    
Install as a Cake Tool

NuLua

Unified Lua5.x/LuaJIT/Luau bindings for .NET and Unity

NuGet Releases license

Hero Image

English | 日本語

Overview

NuLua (pronounced like "new Lua") is a new Lua library for .NET / Unity. It provides common abstractions for using Lua from C#, as well as bindings and high-level APIs for each runtime.

This library is currently provided as a preview. It currently supports Windows/macOS/Linux only; iOS/Android/Web support is planned for the future.

Features

  • High-performance design that minimizes heap allocations on the C# side
  • Modern, easy-to-use API design
  • Choose the backend from Lua 5.1/5.2/5.3/5.4/5.5 and LuaJIT/Luau
  • async/await support

Installation

NuLua requires .NET Standard 2.1 or later.

All packages are distributed on NuGet. To use NuLua, install the core package plus the package for the runtime you want to use.

Package Latest Version
NuLua NuGet Version
NuLua.Lua51 NuGet Version
NuLua.Lua52 NuGet Version
NuLua.Lua53 NuGet Version
NuLua.Lua54 NuGet Version
NuLua.Lua55 NuGet Version
NuLua.LuaJit NuGet Version
NuLua.Luau NuGet Version

Low-level binding APIs and pre-built native binaries can also be added separately. These are included as dependencies of each package, so users normally do not need to install them manually.

Package Latest Version
NuLua.Runtime.Lua51 NuGet Version
NuLua.Runtime.Lua52 NuGet Version
NuLua.Runtime.Lua53 NuGet Version
NuLua.Runtime.Lua54 NuGet Version
NuLua.Runtime.Lua55 NuGet Version
NuLua.Runtime.LuaJit NuGet Version
NuLua.Runtime.Luau NuGet Version
NuLua.Interop.Lua51 NuGet Version
NuLua.Interop.Lua52 NuGet Version
NuLua.Interop.Lua53 NuGet Version
NuLua.Interop.Lua54 NuGet Version
NuLua.Interop.Lua55 NuGet Version
NuLua.Interop.LuaJit NuGet Version
NuLua.Interop.Luau NuGet Version

Platforms

Platform Architecture Supported
Windows x64
arm64
macOS x64
arm64
Linux x64
arm64
iOS 🚧
Android 🚧
Web 🚧

Quick Start

using NuLua;
using NuLua.Lua54;

using var state = Lua54State.Create();
state.OpenLibraries();

var results = state.DoString("return 1 + 2");
Console.WriteLine(results[0]); // 3

ILuaState is not thread-safe. Do not access it from multiple threads simultaneously.

LuaValue

In NuLua, values inside Lua are represented by the LuaValue struct. They can be read with Read<T>().

LuaValue value = state.DoString("return 42")[0];
Console.WriteLine(value.Type);        // Number
Console.WriteLine(value.Read<int>()); // 42

The type mappings between Lua and C# are shown below.

Lua C#
nil LuaValue.Nil
boolean bool
lightuserdata IntPtr
number double, float
string string
table LuaTable
function LuaFunction
userdata T, LuaUserData
thread ILuaState
vector (Luau) System.Numerics.Vector3
buffer (Luau) LuauBuffer

When creating a LuaValue from C#, convertible types are implicitly converted to LuaValue.

LuaValue value;
value = 1.2;                 // double   ->  LuaValue
value = "foo";               // string   ->  LuaValue
value = state.CreateTable(); // LuaTable ->  LuaValue

ILuaState

NuLua provides ILuaState as an abstraction over Lua runtimes.

ILuaState lua55 = Lua55State.Create();
ILuaState luaJit = LuaJitState.Create();
ILuaState luau = LuauState.Create();

This makes it easy to swap the backend runtime without having to account for differences between versions.

Libraries

You can load the standard libraries by calling OpenLibraries(). Individual libraries can also be loaded selectively.

state.OpenLibraries();

state.OpenBaseLibrary();
state.OpenPackageLibrary();
state.OpenTableLibrary();
state.OpenStringLibrary();
state.OpenMathLibrary();
state.OpenCoroutineLibrary();
state.OpenIoLibrary();
state.OpenOsLibrary();
state.OpenUtf8Library();

Global Environment

You can access the Lua global environment using the indexer.

state.DoString("""
    foo = 10
    bar = "hello"
    """);

Console.WriteLine(state["foo"]); // 10
Console.WriteLine(state["bar"]); // hello

state["foo"] = 20;
state["bar"] = "world";

state.DoString("""
    print(foo) -- 20
    print(bar) -- world
    """);

Functions

Lua functions are represented by LuaFunction.

Calling Lua functions from C#

state.DoString("""
    function add(a, b)
        return a + b
    end
    """);

LuaFunction addFunction = state["add"].Read<LuaFunction>();

var results = addFunction.Invoke(1, 2);
Console.WriteLine(results[0]); // 3

Calling C# functions from Lua

var addFunction = state.CreateFunction((state, args) =>
{
    // Read arguments
    var a = args[0].Read<double>();
    var b = args[1].Read<double>();

    // Push return value onto the stack
    state.Push(a + b);

    return 1; // Return the number of return values
});

state["add"] = addFunction;

var results = state.DoString("""
    return add(1, 2)
    """);
Console.WriteLine(results[0]); // 3

When registering a function in the global environment, it is more efficient to call RegisterFunction().

state.RegisterFunction("foo", (state, args) => { ... });

LuaTable

Lua tables are represented by LuaTable.

var table1 = state.CreateTable();
table1[0] = "foo";
table1["a"] = "bar";

state["table1"] = table1;

var table2 = state.DoString("return { a: 10 }")[0].Read<LuaTable>();
Console.WriteLine(table2["a"]); // 10

UserData

C# structs can be passed to Lua as UserData. Structs used as UserData must be unmanaged (they must not contain references).

Use state.CreateUserData<T>() to create UserData. The returned LuaUserData is a handle that holds information such as the UserData pointer and size.

LuaUserData userdata = state.CreateUserData<Example>(new()
{
    Foo = 5,
    Bar = 1.5,
});

struct Example
{
    public int Foo;
    public double Bar;
}

A LuaValue representing UserData can be read directly with Read<T>().

var value = state["example"]; // userdata
var example = value.Read<Example>();

Threads / Coroutines

Lua threads are represented by ILuaState.

You can create threads that share the global environment using state.CreateThread(). This is useful when running multiple independent Lua scripts.

var thread = state.CreateThread();
thread.DoString("return 1 + 2");

You can also obtain Lua coroutines as ILuaState and control them from C#.

-- coroutine.lua

local co = coroutine.create(function()
    for i = 1, 10 do
        print(i)
        coroutine.yield()
    end
end)

return co
state.OpenCoroutineLibrary();

var bytes = File.ReadAllBytes("coroutine.lua");
var results = state.DoString(bytes);
var co = results[0].Read<ILuaState>();

for (int i = 0; i < 10; i++)
{
    var resumeResults = co.Resume(state);

    // Like coroutine.resume(), the first element is true on success, followed by the function's return values
    // 1, 2, 3, 4, ...
    Console.WriteLine(resumeResults[1]);
}

Module Resolution

You can replace Lua's module resolution with a custom implementation using LuaModuleLoader. Built-in loaders include FileSystemModuleLoader and InMemoryModuleLoader.

state.OpenPackageLibrary();

state.UseModuleLoader(new FileSystemModuleLoader("path/to/lua/modules"));
state.UseModuleLoader(new InMemoryModuleLoader(new Dictionary<string, string>
{
    ["foo"] = "return 42",
    ["bar"] = "return 'hello'",
}));

UseModuleLoader() must be called after OpenPackageLibrary(). In Luau, OpenPackageLibrary() does not exist, so it works by replacing require().

Asynchronous API

Lua script execution itself always completes synchronously, but C# functions passed to Lua can be asynchronous.

state.RegisterFunction("wait", async (state, args) =>
{
    var sec = args[0].Read<double>();
    await Task.Delay(TimeSpan.FromSeconds(sec));
    return 0;
});

When executing a Lua script that contains calls to asynchronous functions, the caller must also use the asynchronous API.

await state.DoStringAsync("""
    wait(2)
    print("delayed")
    """);

// This causes a runtime error
state.DoString("""
    wait(2)
    print("delayed")
    """);

Asynchronous functions cannot be set as metamethods. These must always be synchronous functions.

Debug

You can access Lua's debug API through ILuaState.Debug.

Retrieving Stack Information

GetStackDepth() returns the depth of the call stack. TryGetStackInfo() retrieves stack information for the specified level.

int depth = state.Debug.GetStackDepth();

if (state.Debug.TryGetStackInfo(0, LuaDebugInfoFields.All, out var info))
{
    Console.WriteLine(info.Name);
    Console.WriteLine(info.Source);
    Console.WriteLine(info.CurrentLine);
}

Local Variables / Upvalues

You can get and set local variables and upvalues using GetLocal()/SetLocal() and GetUpvalue()/SetUpvalue().

// Push the function onto the stack first
var name = state.Debug.GetUpvalue(-1, 1);

Hooks

You can use SetHook() to set callbacks for each function call or line executed.

state.SetHook((s, ev, line) =>
{
    Console.WriteLine($"{ev}: {line}");
}, LuaHookMask.Line, 0);

To remove the hook, pass null and LuaHookMask.None.

state.SetHook(null, LuaHookMask.None, 0);

SetHook() is not supported in Luau. Use the Luau debug API instead.

Garbage Collection

You can control Lua's GC through ILuaState.GarbageCollection.

var before = state.GarbageCollection.GetByteCount();
state.GarbageCollection.Collect();
var after = state.GarbageCollection.GetByteCount();

// Step the GC
bool finished = state.GarbageCollection.Step(1);

// Stop and restart the GC
state.GarbageCollection.Stop();
Console.WriteLine(state.GarbageCollection.IsRunning()); // False
state.GarbageCollection.Restart();

IsRunning() is not supported in Lua 5.1.

Low-level API

You can also perform stack operations directly by calling the low-level API of ILuaState.

state.Push(1);
state.Push(2);
state.Arith(LuaArithOp.Add);
var result = state.ToNumber(-1);
Console.WriteLine(result); // 3

state.Push("foo");
state.Push("bar");
state.Concat(2);
var strResult = state.ToString(-1);
Console.WriteLine(strResult); // foobar

LuaJIT

NuLua.LuaJit provides additional APIs for LuaJIT-specific features.

Libraries

LuaJitState supports LuaJIT's extension libraries.

using NuLua;
using NuLua.LuaJit;

using var state = LuaJitState.Create();

state.OpenFfiLibrary();
state.OpenBitLibrary();
state.OpenJitLibrary();

TrySetJitMode

You can use TrySetJitMode() to set the JIT compiler mode.

state.TrySetJitMode(0, LuaJitFlags.Engine | LuaJitFlags.Off);

Luau

NuLua.Luau provides additional APIs for Luau-specific features.

Libraries

LuauState supports Luau's extension libraries.

using NuLua;
using NuLua.Luau;

using var state = LuauState.Create();
state.OpenBufferLibrary();
state.OpenVectorLibrary();

LuauBuffer

Luau's buffer type is represented by LuauBuffer.

state.OpenBufferLibrary();

var results = state.DoString("return buffer.fromstring('hello')");
var buffer = results[0].Read<LuauBuffer>();

Console.WriteLine(Encoding.UTF8.GetString(buffer.AsSpan())); // hello

You can also create buffers from C#.

var buffer = state.CreateBuffer(10);

var span = buffer.AsSpan();
span[0] = (byte)'1';
span[1] = (byte)'2';
span[2] = (byte)'3';
span[3] = (byte)'4';
span[4] = (byte)'5';
"hello"u8.CopyTo(span[5..]);

state["b"] = buffer;
var results = state.DoString("return buffer.tostring(b)");
Console.WriteLine(results[0]); // 12345hello

LuauCompiler

TryDump() and Dump() are not supported in LuauState.

using var state = LuauState.Create();
state.Dump(index, strip); // NotSupportedException

If you want to compile Luau code to bytecode, use LuauCompiler instead.

byte[] bytecode = LuauCompiler.Compile("return 1 + 2");

Sandbox

Luau provides APIs for sandboxing threads. These are available via CreateSandbox() and CreateSandboxThread().

using var state = LuauState.CreateSandbox();
var thread = state.CreateSandboxThread();

Debug

In Luau, UpvalueId(), UpvalueJoin(), and SetHook() are not available. Instead, you can use Luau's own debug API.

Retrieving Arguments

GetArgument() pushes the nth argument of the function call at the specified level onto the stack. The return value is the number of values pushed.

int pushed = state.Debug.GetArgument(1, 1);
Single Stepping

When SetSingleStep() is set to true, the callback registered with SetDebugStepCallback() is called for each instruction executed. Debug information must be enabled to use this.

state.Debug.SetSingleStep(true);
state.Debug.SetDebugStepCallback((s, ev, line) =>
{
    Console.WriteLine($"step: {line}");
});

// Pass null to remove the callback
state.Debug.SetDebugStepCallback(null);
state.Debug.SetSingleStep(false);
Breakpoints

You can use SetBreakpoint() to set a breakpoint on a function on the stack. funcIndex is the stack index where the function is located, line is the line number, and enabled specifies whether it is enabled.

state.Debug.SetBreakpoint(-1, 10, true);

SetDebugBreakCallback() registers a callback that is called when a BREAK instruction is reached.

state.Debug.SetDebugBreakCallback((s, ev, line) =>
{
    Console.WriteLine($"break: {line}");
});
Debug Trace

GetDebugTrace() retrieves the current call stack as a string.

string trace = state.Debug.GetDebugTrace();
Console.WriteLine(trace);
Coverage

GetCoverage() collects execution counts per line for the specified function.

state.Debug.GetCoverage(-1, entry =>
{
    Console.WriteLine($"function: {entry.Function}, line: {entry.LineDefined}");
    for (int i = 0; i < entry.Hits.Length; i++)
    {
        Console.WriteLine($"  line {entry.LineDefined + i}: {entry.Hits[i]}");
    }
});
Thread Interrupt Callback

SetDebugInterruptCallback() registers a callback that is called when execution of another thread is interrupted.

state.Debug.SetDebugInterruptCallback((s, ev, line) =>
{
    Console.WriteLine("interrupted");
});

Unity

TODO

License

This library is released under the MIT License.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed.  net11.0 is compatible. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (7)

Showing the top 5 NuGet packages that depend on NuLua:

Package Downloads
NuLua.Lua55

NuLua High-level API for Lua 5.5

NuLua.Lua53

NuLua High-level API for Lua 5.3

NuLua.Lua54

NuLua High-level API for Lua 5.4

NuLua.LuaJit

NuLua High-level API for LuaJIT

NuLua.Lua52

NuLua High-level API for Lua 5.2

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0 296 6/22/2026
0.0.3 324 6/22/2026
0.0.2 331 6/22/2026
0.0.1 327 6/20/2026